<?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[Elasticsearch Labs]]></title>
        <description><![CDATA[Articles et tutoriels de l'équipe Search d'Elastic]]></description>
        <link>https://www.elastic.co/search-labs/fr</link>
        <image>
            <url>https://www.elastic.co/search-labs/favicon.svg</url>
            <title>Elasticsearch Labs</title>
            <link>https://www.elastic.co/search-labs/fr</link>
        </image>
        <generator>Elasticsearch Labs</generator>
        <lastBuildDate>Fri, 03 Apr 2026 21:11:02 GMT</lastBuildDate>
        <atom:link href="https://www.elastic.co/search-labs/fr/rss/feed" rel="self" type="application/rss+xml"/>
        <copyright><![CDATA[© 2026. Elasticsearch B.V. Tous droits réservés]]></copyright>
        <language><![CDATA[fr]]></language>
        <item>
            <title><![CDATA[Création d'un serveur Elasticsearch MCP avec TypeScript]]></title>
            <description><![CDATA[Apprenez à créer un serveur MCP Elasticsearch avec TypeScript et Claude Desktop.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elastic-mcp-server-typescript-claude</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elastic-mcp-server-typescript-claude</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0bb24c05fff391f42070c2883182ea6fe9cb9680-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Lorsque vous travaillez avec de grandes bases de connaissances dans Elasticsearch, trouver des informations n’est que la moitié du travail. Les ingénieurs ont souvent besoin de synthétiser des résultats issus de plusieurs documents, de générer des résumés et de faire remonter les réponses à leur source. Model Context Protocol (MCP) fournit un moyen standardisé de connecter Elasticsearch à des applications alimentées par des grands modèles de langage (LLM) afin d’y parvenir. Bien qu’Elastic propose des solutions officielles, comme Elastic Agent Builder (qui inclut un <a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">point de terminaison MCP</a> parmi ses fonctionnalités), la création d’un serveur MCP personnalisé vous offre un contrôle total sur la logique de recherche, la mise en forme des résultats et la manière dont le contenu récupéré est transmis à un LLM pour la synthèse, les résumés et les citations.</p><p>Dans cet article, nous examinerons les avantages de la création d’un serveur MCP Elasticsearch personnalisé et expliquerons comment en créer un en TypeScript pour connecter Elasticsearch aux applications alimentées par des modèles LLM.</p><h2>Pourquoi créer un serveur Elasticsearch MCP personnalisé ?</h2><p>Elastic propose quelques alternatives pour <a href="https://www.elastic.co/docs/solutions/search/mcp">les serveurs MCP</a> :</p><ul><li><a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">Serveur MCP Elastic Agent Builder pour Elasticsearch 9.2+</a></li><li><a href="https://github.com/elastic/mcp-server-elasticsearch?tab=readme-ov-file#elasticsearch-mcp-server">Serveur MCP Elasticsearch pour les anciennes versions (Python)</a></li></ul><p>Si vous avez besoin de plus de contrôle sur la façon dont votre serveur MCP interagit avec Elasticsearch, la création de votre propre serveur personnalisé vous donne la flexibilité de l&#x27;adapter exactement à vos besoins. Par exemple, le point de terminaison MCP d&#x27;Agent Builder est limité aux requêtes du langage de requête Elasticsearch (ES|QL), tandis qu&#x27;un serveur personnalisé vous permet d&#x27;utiliser le langage de requête DSL complet. Vous gagnez également le contrôle sur la façon dont les résultats sont formatés avant d&#x27;être transmis au LLM et pouvez intégrer des étapes de traitement supplémentaires, comme la summarisation alimentée par OpenAI que nous mettrons en œuvre dans ce tutoriel.</p><p>À la fin de cet article, vous aurez un serveur MCP dans TypeScript qui recherche les informations stockées dans un index Elasticsearch, les résume et fournit des citations. Nous utiliserons Elasticsearch pour la récupération, le modèle <code>gpt-4o-mini</code> d&#x27;OpenAI pour résumer et générer des citations, et Claude Desktop comme client MCP et interface utilisateur pour recevoir les requêtes des utilisateurs et fournir des réponses. Le résultat final est un assistant de connaissances interne qui aide les ingénieurs à découvrir et à synthétiser les bonnes pratiques dans l&#x27;ensemble de la documentation technique de leur organisation.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ec5771a874cf9740d4cac6888622cbe8cd6aede7-1999x1133.png" style="width: 100%;" /><h2>Produits requis</h2><ul><li>Node.js 20 +</li><li>Elasticsearch</li><li>Clé API OpenAI</li><li>Claude Desktop</li></ul><h3>Qu&#x27;est-ce que le MCP ?</h3><p><a href="https://www.elastic.co/what-is/mcp">MCP</a> est une norme ouverte, créée par <a href="https://www.anthropic.com/news/model-context-protocol">Anthropic</a>, qui fournit des connexions bidirectionnelles sécurisées entre les LLM et les systèmes externes, comme Elasticsearch. Vous pouvez en savoir plus sur l&#x27;état actuel du MCP dans <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">cet article</a>.</p><p>Le paysage des MCP <a href="https://www.elastic.co/search-labs/blog/mcp-current-state#mcp-project-updates:-transport,-elicitation,-and-structured-tooling">évolue chaque jour</a>, avec des serveurs disponibles pour un large éventail de cas d&#x27;utilisation. De plus, il est facile de créer votre propre serveur MCP personnalisé, comme nous le montrerons dans cet article.</p><h3>Clients MCP</h3><p>Il existe une longue <a href="https://modelcontextprotocol.io/clients">liste de clients MCP disponibles</a>, chacun ayant ses propres caractéristiques et limitations. Par souci de simplicité et de popularité, nous utiliserons <a href="https://claude.ai/download">Claude Desktop</a> comme client MCP. Il servira d&#x27;interface de chat où les utilisateurs pourront poser des questions en langage naturel, et il invoquera automatiquement les outils exposés par notre serveur MCP pour rechercher des documents et générer des résumés.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/66eb0b11473347b6cf2d85718251eeac38d6249d-1999x1491.png" style="width: 100%;" /><h2>Créer un serveur Elasticsearch MCP</h2><p>Grâce au <a href="https://github.com/modelcontextprotocol/typescript-sdk">SDK TypeScript</a>, nous pouvons facilement créer un serveur qui comprend comment interroger nos données Elasticsearch à partir d&#x27;une requête utilisateur.</p><p>Voici les étapes dans cet article pour intégrer le serveur Elasticsearch MCP avec le client Claude Desktop :</p><ol><li><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#configure-mcp-server-for-elasticsearch">Configurer le serveur MCP pour Elasticsearch.</a></li><li><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#load-the-mcp-server-into-claude-desktop">Chargez le serveur MCP dans Claude Desktop.</a></li><li><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#test-it-out">Testez-le.</a></li></ol><h3>Configurez le serveur MCP pour Elasticsearch</h3><p>Pour commencer, initialisons une application Node :</p><pre><code>npm init -y</code></pre><p>Cela créera un fichier <code>package.json</code>, et avec lui, nous pourrons commencer à installer les dépendances nécessaires pour cette application.</p><pre><code>npm install @elastic/elasticsearch @modelcontextprotocol/sdk openai zod && npm install --save-dev ts-node @types/node typescript</code></pre><ul><li><strong>@elastic/elasticsearch</strong> nous donnera accès à la bibliothèque de Node.js Elasticsearch.</li><li><strong>@modelcontextprotocol/sdk</strong> fournit les outils du noyau pour créer et gérer un serveur MCP, enregistrer les outils et gérer la communication avec les clients MCP.</li><li><strong>OpenAI</strong> permet d&#x27;interagir avec les modèles OpenAI pour générer des résumés ou des réponses en langage naturel.</li><li><strong><a href="https://zod.dev/">ZOD</a> </strong>aide à définir et valider des schémas structurés pour les données d’entrée et de sortie dans chaque outil.</li></ul><p><code>ts-node</code>, <code>@types/node</code> et <code>typescript</code> seront utilisés pendant le développement pour écrire le code et compiler les scripts.</p><h4>Configurer l’ensemble de données</h4><p>Pour fournir les données que Claude Desktop peut interroger via notre serveur MCP, nous utiliserons un <a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/dataset.json">ensemble de données simulé de base de connaissances interne</a>. Voici à quoi ressemblera un document issu de cet ensemble de données :</p><pre><code>{
    "id": 5,
    "title": "Logging Standards for Microservices",
    "content": "Consistent logging across microservices helps with debugging and tracing. Use structured JSON logs and include request IDs and timestamps. Avoid logging sensitive information. Centralize logs in Elasticsearch or a similar system. Configure log rotation to prevent storage issues and ensure logs are searchable for at least 30 days.",
    "tags": ["logging", "microservices", "standards"]
}</code></pre><p>Pour ingérer les données, nous avons préparé un script qui crée un index dans Elasticsearch et y charge l’ensemble de données. Vous pouvez le trouver <a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/setup.ts">ici</a>.</p><h4>Serveur MCP</h4><p>Créez un fichier nommé <a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/index.ts"><code>index.ts</code></a> et ajoutez le code suivant pour importer les dépendances et gérer les variables d’environnement :</p><pre><code>// index.ts
import { z } from "zod";
import { Client } from "@elastic/elasticsearch";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import OpenAI from "openai";

const ELASTICSEARCH_ENDPOINT =
  process.env.ELASTICSEARCH_ENDPOINT ?? "http://localhost:9200";
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY ?? "";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? "";
const INDEX = "documents";</code></pre><p>Aussi, initialisons les clients pour gérer les appels Elasticsearch et OpenAI :</p><pre><code>const openai = new OpenAI({
  apiKey: OPENAI_API_KEY,
});

const _client = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: ELASTICSEARCH_API_KEY,
  },
});</code></pre><p>Pour rendre notre implémentation plus robuste et garantir des entrées et des sorties structurées, nous définirons des schémas en utilisant <a href="https://zod.dev/"><code>zod</code></a>. Cela nous permet de valider les données au moment de l&#x27;exécution, de détecter les erreurs tôt et de rendre les réponses des outils plus faciles à traiter de manière programmatique :</p><pre><code>const DocumentSchema = z.object({
  id: z.number(),
  title: z.string(),
  content: z.string(),
  tags: z.array(z.string()),
});

const SearchResultSchema = z.object({
  id: z.number(),
  title: z.string(),
  content: z.string(),
  tags: z.array(z.string()),
  score: z.number(),
});

type Document = z.infer<typeof DocumentSchema>;
type SearchResult = z.infer<typeof SearchResultSchema>;</code></pre><p>Pour en savoir plus sur les sorties structurées, cliquez <a href="https://www.elastic.co/search-labs/blog/structured-outputs-elasticsearch-guide">ici</a>.</p><p>Maintenant, initialisons le serveur MCP :</p><pre><code>const server = new McpServer({
  name: "Elasticsearch RAG MCP",
  description:
    "A RAG server using Elasticsearch. Provides tools for document search, result summarization, and source citation.",
  version: "1.0.0",
});</code></pre><h4>Définition des outils MCP</h4><p>Une fois que tout est configuré, nous pouvons commencer à écrire les outils qui seront exposés par notre serveur MCP. Ce serveur expose deux outils :</p><ul><li><strong><code>search_docs</code>: </strong>Recherche des documents dans Elasticsearch à l&#x27;aide de la recherche full-text.</li><li><strong><code>summarize_and_cite</code>:</strong> Résume et synthétise les informations provenant de documents précédemment récupérés pour répondre à la question d&#x27;un utilisateur. Cet outil ajoute également des citations faisant référence aux documents sources.</li></ul><p>Ensemble, ces outils forment un workflow simple de « récupération puis synthèse », où un outil extrait les documents pertinents et l&#x27;autre utilise ces documents pour générer une réponse synthétisée et citée.</p><h4>Format de réponse de l&#x27;outil</h4><p>Chaque outil peut accepter des paramètres d&#x27;entrée arbitraires, mais il doit répondre avec la structure suivante :</p><ul><li><strong>Contenu :</strong> il s&#x27;agit de la réponse de l&#x27;outil dans un format non structuré. Ce champ est généralement utilisé pour renvoyer du texte, des images, de l’audio, des liens ou des plongements. Pour cette application, il sera utilisé pour renvoyer un texte formaté contenant les informations générées par les outils.</li><li><strong>structuredContent : </strong>il s&#x27;agit d&#x27;un retour facultatif utilisé pour fournir les résultats de chaque outil dans un format structuré. Ceci est utile à des fins de programmation. Bien qu&#x27;il ne soit pas utilisé dans ce serveur MCP, il peut être utile si vous souhaitez développer d&#x27;autres outils ou traiter les résultats de manière programmée.</li></ul><p>En gardant cette structure à l’esprit, entrons dans le vif du sujet en examinant chaque outil en détail.</p><h4>Outil de recherche</h4><p>Cet outil effectue une <a href="https://www.elastic.co/docs/solutions/search/full-text">recherche full-text</a> dans l’index Elasticsearch pour récupérer les documents les plus pertinents selon la requête de l’utilisateur. Il met en évidence les correspondances clés et offre un aperçu rapide avec des scores de pertinence.</p><pre><code>server.registerTool(
  "search_docs",
  {
    title: "Search Documents",
    description:
      "Search for documents in Elasticsearch using full-text search. Returns the most relevant documents with their content, title, tags, and relevance score.",
    inputSchema: {
      query: z
        .string()
        .describe("The search query terms to find relevant documents"),
      max_results: z
        .number()
        .optional()
        .default(5)
        .describe("Maximum number of results to return"),
    },
    outputSchema: {
      results: z.array(SearchResultSchema),
      total: z.number(),
    },
  },
  async ({ query, max_results }) => {
    if (!query) {
      return {
        content: [
          {
            type: "text",
            text: "Query parameter is required",
          },
        ],
        isError: true,
      };
    }

    try {
      const response = await _client.search({
        index: INDEX,
        size: max_results,
        query: {
          bool: {
            must: [
              {
                multi_match: {
                  query: query,
                  fields: ["title^2", "content", "tags"],
                  fuzziness: "AUTO",
                },
              },
            ],
            should: [
              {
                match_phrase: {
                  title: {
                    query: query,
                    boost: 2,
                  },
                },
              },
            ],
          },
        },
        highlight: {
          fields: {
            title: {},
            content: {},
          },
        },
      });

      const results: SearchResult[] = response.hits.hits.map((hit: any) => {
        const source = hit._source as Document;

        return {
          id: source.id,
          title: source.title,
          content: source.content,
          tags: source.tags,
          score: hit._score ?? 0,
        };
      });

      const contentText = results
        .map(
          (r, i) =>
            `[${i + 1}] ${r.title} (score: ${r.score.toFixed(
              2,
            )})\n${r.content.substring(0, 200)}...`,
        )
        .join("\n\n");

      const totalHits =
        typeof response.hits.total === "number"
          ? response.hits.total
          : (response.hits.total?.value ?? 0);

      return {
        content: [
          {
            type: "text",
            text: `Found ${results.length} relevant documents:\n\n${contentText}`,
          },
        ],
        structuredContent: {
          results: results,
          total: totalHits,
        },
      };
    } catch (error: any) {
      console.log("Error during search:", error);

      return {
        content: [
          {
            type: "text",
            text: `Error searching documents: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);</code></pre><p><em>Nous configurons <code><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-fuzzy-query">fuzziness</a>: “AUTO”</code> pour que la tolérance aux fautes de frappe soit variable en fonction de la longueur du jeton analysé. Nous configurons également <code>title^2</code> pour qu&#x27;il augmente le score des documents dont la correspondance se fait sur le champ du titre.</em></p><h4>outil summarize_and_cite</h4><p>Cet outil génère un résumé basé sur les documents récupérés lors de la recherche précédente. Il utilise le modèle <code>gpt-4o-mini</code> d’OpenAI pour synthétiser les informations les plus pertinentes afin de répondre à la question de l’utilisateur, en fournissant des réponses dérivées directement des résultats de recherche. Outre le résumé, il renvoie également les métadonnées de citation des documents sources utilisés.</p><pre><code>server.registerTool(
  "summarize_and_cite",
  {
    title: "Summarize and Cite",
    description:
      "Summarize the provided search results to answer a question and return citation metadata for the sources used.",
    inputSchema: {
      results: z
        .array(SearchResultSchema)
        .describe("Array of search results from search_docs"),
      question: z.string().describe("The question to answer"),
      max_length: z
        .number()
        .optional()
        .default(500)
        .describe("Maximum length of the summary in characters"),
      max_docs: z
        .number()
        .optional()
        .default(5)
        .describe("Maximum number of documents to include in the context"),
    },
    outputSchema: {
      summary: z.string(),
      sources_used: z.number(),
      citations: z.array(
        z.object({
          id: z.number(),
          title: z.string(),
          tags: z.array(z.string()),
          relevance_score: z.number(),
        })
      ),
    },
  },
  async ({ results, question, max_length, max_docs }) => {
    if (!results || results.length === 0 || !question) {
      return {
        content: [
          {
            type: "text",
            text: "Both results and question parameters are required, and results must not be empty",
          },
        ],
        isError: true,
      };
    }

    try {
      const used = results.slice(0, max_docs);

      const context = used
        .map(
          (r: SearchResult, i: number) =>
            `[Document ${i + 1}: ${r.title}]\\n${r.content}`
        )
        .join("\n\n---\n\n");

      // Generate summary with OpenAI
      const completion = await openai.chat.completions.create({
        model: "gpt-4o-mini",
        messages: [
          {
            role: "system",
            content:
              "You are a helpful assistant that answers questions based on provided documents. Synthesize information from the documents to answer the user's question accurately and concisely. If the documents don't contain relevant information, say so.",
          },
          {
            role: "user",
            content: `Question: ${question}\\n\\nRelevant Documents:\\n${context}`,
          },
        ],
        max_tokens: Math.min(Math.ceil(max_length / 4), 1000),
        temperature: 0.3,
      });

      const summaryText =
        completion.choices[0]?.message?.content ?? "No summary generated.";

      const citations = used.map((r: SearchResult) => ({
        id: r.id,
        title: r.title,
        tags: r.tags,
        relevance_score: r.score,
      }));

      const citationText = citations
        .map(
          (c: any, i: number) =>
            `[${i + 1}] ID: ${c.id}, Title: "${c.title}", Tags: ${c.tags.join(
              ", ",
            )}, Score: ${c.relevance_score.toFixed(2)}`,
        )
        .join("\n");

      const combinedText = `Summary:\\n\\n${summaryText}\\n\\nSources used (${citations.length}):\\n\\n${citationText}`;

      return {
        content: [
          {
            type: "text",
            text: combinedText,
          },
        ],
        structuredContent: {
          summary: summaryText,
          sources_used: citations.length,
          citations: citations,
        },
      };
    } catch (error: any) {
      return {
        content: [
          {
            type: "text",
            text: `Error generating summary and citations: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);</code></pre><p>Enfin, il faut démarrer le serveur avec <a href="https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#stdio">stdio</a>. Cela signifie que le client MCP communiquera avec notre serveur en lisant et en écrivant dans ses flux d&#x27;entrée et de sortie standard. StDIO est l’option de transport la plus simple et fonctionne bien pour les serveurs MCP locaux lancés en sous-processus par le client. Ajoutez le code suivant à la fin du fichier :</p><pre><code>const transport = new StdioServerTransport();
server.connect(transport);</code></pre><p>Compilez le projet en utilisant la commande suivante :</p><pre><code>npx tsc index.ts --target ES2022 --module node16 --moduleResolution node16 --outDir ./dist --strict --esModuleInterop</code></pre><p>Cela créera un dossier <code>dist</code>, dans lequel se trouvera un fichier <code>index.js</code>.</p><h3>Chargez le serveur MCP dans Claude Desktop.</h3><p>Suivez <a href="https://modelcontextprotocol.io/docs/develop/connect-local-servers">ce guide</a> pour configurer le serveur MCP avec Claude Desktop. Dans le fichier de configuration Claude, nous devons définir les valeurs suivantes :</p><pre><code>{
  "mcpServers": {
    "elasticsearch-rag-mcp": {
      "command": "node",
      "args": [   "/Users/user-name/app-dir/dist/index.js"
      ],
      "env": {
        "ELASTICSEARCH_ENDPOINT": "your-endpoint-here",
        "ELASTICSEARCH_API_KEY": "your-api-key-here",
        "OPENAI_API_KEY": "your-openai-key-here"
      }
    }
  }
}</code></pre><p>La valeur <code>args</code> doit pointer vers le fichier compilé dans le dossier <code>dist</code> . Vous devez également définir les variables d&#x27;environnement dans le fichier de configuration avec les noms exacts définis dans le code.</p><h3>Testez-le</h3><p>Avant d’exécuter chaque outil, cliquez sur <strong>Recherche et Outils</strong> pour vous assurer que les outils sont activés. Vous pouvez également activer ou désactiver chaque option ici :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/172981c2a54adabc70d5819013c3007670935605-1999x1002.png" style="width: 100%;" /><p>Enfin, testons le serveur MCP depuis le chat Claude Desktop et commençons à poser des questions :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03654c0f8c53c714f801fba8b25747071179209b-1999x1353.png" style="width: 100%;" /><p>Pour la question « <strong>Recherche de documents sur les méthodes d’authentification et le contrôle d’accès basé sur les rôles</strong> », l’outil <code>search_docs</code> est exécuté et renvoie les résultats suivants :</p><pre><code>Most Relevant Documents:
Access Control and Role Management (highest relevance) - This document covers role-based access control (RBAC) principles, including ensuring users only have necessary permissions, regular auditing of user roles, revoking inactive accounts, and implementing just-in-time access for sensitive operations.
User Authentication with OAuth 2.0 - This document explains OAuth 2.0 authentication, which enables secure delegated access without credential sharing. It covers configuring identity providers, token management with limited scope and lifetime, and secure storage of refresh tokens.
Container Security Guidelines - While primarily about container security, this document touches on access control aspects like running containers as non-root users and avoiding embedded credentials.
Incident Response Playbook - This mentions role assignment during incidents (incident commander, communications lead, etc.), which relates to access control in emergency scenarios.
Logging Standards for Microservices - This document includes guidance on avoiding logging sensitive information, which is relevant to authentication security.</code></pre><p>La réponse est : « Super ! J&#x27;ai trouvé 5 documents pertinents sur les méthodes d&#x27;authentification et le contrôle d&#x27;accès basé sur les rôles. Voici ce qui a été découvert : »</p><p>L&#x27;appel d&#x27;outil renvoie les documents sources dans le cadre de sa charge utile de réponse, qui sont ensuite utilisés pour générer des citations.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25ff4c7e9563d99752f95540dafdc7fd211a66e3-800x530.gif" style="width: 100%;" /><p>Il est également possible d&#x27;enchaîner plusieurs outils dans une même interaction. Dans ce cas, Claude Desktop analyse la question de l’utilisateur et détermine qu’il doit d’abord appeler <code>search_docs</code> pour récupérer les documents pertinents, puis transmettre ces résultats à <code>summarize_and_cite</code> pour générer la réponse finale, le tout sans nécessiter d’invites séparées de la part de l’utilisateur :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6a8a46acb4236e77058f18bcd2f0737b5882c05-1999x1101.png" style="width: 100%;" /><p>Dans ce cas, pour la requête « <strong>Quelles sont les principales recommandations pour améliorer l’authentification et le contrôle d’accès dans l’ensemble de nos systèmes ? Veuillez inclure des références.</strong> », Nous avons obtenu les résultats suivants :</p><pre><code>Based on the documentation, here are the main recommendations to improve authentication and access control across your systems:

Key Recommendations

1. Implement Role-Based Access Control (RBAC)
Ensure users have only the permissions necessary for their job functions. This principle of least privilege minimizes security risks by limiting access to what's truly needed. [1]

2. Regular Access Audits
Conduct regular audits of user roles and promptly revoke access for inactive accounts. This helps prevent unauthorized access from dormant or compromised accounts. [1]

3. Just-in-Time (JIT) Access
Implement just-in-time access for sensitive operations, where permissions are granted only when needed and for a limited duration. This reduces the attack surface by minimizing standing privileges. [1]

4. OAuth 2.0 for Secure Authentication
Use OAuth 2.0 to enable secure delegated access without sharing user credentials. This protocol allows third-party applications to access resources on behalf of users without exposing passwords. [2]

5. Token Security and Management
Configure your identity provider to issue tokens with limited scope and lifetime

Store refresh tokens securely
Validate access tokens consistently to maintain security [2]
References

Access Control and Role Management (Tags: security, access-control)
User Authentication with OAuth 2.0 (Tags: authentication, oauth)
These recommendations work together to create a defense-in-depth approach, where multiple security layers protect your systems from unauthorized access.</code></pre><p>Comme à l’étape précédente, nous pouvons voir la réponse de chaque outil à cette question :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cb606d356b2f7d5e4878a5eff71bc881869ac0ee-800x585.gif" style="width: 100%;" /><p><em>Note : Si un sous-menu apparaît demandant si vous approuvez l’utilisation de chaque outil, sélectionnez <strong>Toujours autoriser</strong> ou <strong>Permettre une fois</strong>.</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aea942ba9b0037526ea215bec65690f1a5c3099c-1522x250.png" style="width: 100%;" /><h2>Conclusion</h2><p>Les serveurs MCP représentent une étape importante vers la standardisation des outils LLM pour les applications locales et distantes. Bien que la compatibilité totale soit encore en cours de développement, nous avançons rapidement dans cette direction.</p><p>Dans cet article, nous avons appris à créer un serveur MCP personnalisé en TypeScript qui connecte Elasticsearch aux applications basées sur LLM. Notre serveur propose deux outils : <code>search_docs</code> pour récupérer les documents pertinents à l&#x27;aide de Query DSL ; et <code>summarize_and_cite</code> pour générer des résumés avec des citations via des modèles OpenAI et Claude Desktop comme interface utilisateur client.</p><p>L&#x27;avenir de la compatibilité entre les différents fournisseurs côté client et côté serveur semble prometteur. Les prochaines étapes consistent à ajouter davantage de fonctionnalités et de flexibilité à votre agent. Vous trouverez un <a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">article</a> pratique expliquant comment paramétrer vos requêtes à l&#x27;aide de modèles de rechercher pour gagner en précision et en flexibilité.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Annonce des autorisations en lecture seule pour les tableaux de bord Kibana]]></title>
            <description><![CDATA[Présentation des tableaux de bord en lecture seule dans Kibana, offrant aux créateurs de tableaux de bord des contrôles de partage détaillés pour garantir l'exactitude des résultats et les protéger contre les modifications indésirables.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/kibana-dashboards-read-only-permissions</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/kibana-dashboards-read-only-permissions</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Fabio Busatto, Teresa Alvarez Soler]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/11e578bc317aea30d2e10ccc0334a532f6af2ef9-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Vous connaissez la situation. Vous passez une heure à créer le tableau de bord parfait pour suivre vos logs : chaque graphique, chaque filtre, chaque étiquette. Vous le partagez avec votre équipe. Quelques jours plus tard, vous l&#x27;ouvrez et quelque chose cloche. Un collègue a modifié une requête. Ou quelqu&#x27;un a changé la plage de dates. Ils pensaient sûrement bien faire. Vous voilà maintenant à éplucher les modifications et à remettre en question chaque chiffre. Ça vous dit quelque chose ?</p><p>C&#x27;est précisément pour cela que nous avons créé des <strong>tableaux de bord en lecture seule</strong>. C&#x27;est le niveau de contrôle que vous attendiez. Partagez vos tableaux de bord en toute confiance, sans craindre que la prochaine personne qui dispose d&#x27;un accès en modification ne les modifie ou ne les altère.</p><p>Remarque : les autorisations en lecture seule sont disponibles dans Elastic Cloud Serverless et à partir de la version 9.3 pour Elastic Cloud Hosted et Elastic autogéré.</p><h2>Quand l&#x27;option &quot;tout le monde peut modifier&quot; pose problème</h2><p>Dans Kibana, le <em>partage </em>est généralement synonyme d&#x27;autorisations au niveau de l&#x27;espace. Si quelqu&#x27;un peut créer des tableaux de bord dans un espace, il peut également modifier ou supprimer ceux des autres. C&#x27;est génial pour collaborer jusqu&#x27;à ce que ce ne soit plus le cas. Une modification accidentelle peut entraîner de mauvaises décisions, une perte de confiance et beaucoup de nettoyage.</p><p>Nous avons entendu les solutions de contournement : <strong>&quot;On ajoute &quot;lecture seule&quot; dans le nom du tableau de bord et on espère que les gens le remarqueront.&quot;</strong> Ou encore : <strong>&quot;On les étiquette et on croise les doigts.&quot;</strong> L&#x27;espoir n&#x27;est pas un modèle d&#x27;autorisations. Il vous fallait un moyen efficace de verrouiller un tableau de bord sans en interdire l&#x27;accès à tous.</p><h2>Ce qui ne va pas</h2><p>Deb et Kevin ont tous deux un accès en modification au tableau de bord de surveillance des logs dans l&#x27;espace Opérations. Kevin apporte quelques modifications aux graphiques. À son retour, Deb constate que les chiffres ne correspondent plus à ce qu&#x27;elle a présenté. Elle doit alors rechercher ce qui a été modifié (souvent de mémoire), le corriger et se demander combien de rapports erronés ont été diffusés.</p><h2>Tableaux de bord en lecture seule : droits d&#x27;accès et contrôle adaptés</h2><p>Les tableaux de bord en lecture seule résolvent ce problème en vous permettant de contrôler si d&#x27;autres utilisateurs peuvent les modifier. Lorsque vous partagez un tableau de bord, vous avez le choix entre : <strong>modifier</strong> (par défaut, comme aujourd&#x27;hui) ou <strong>afficher</strong>. En mode <strong>affichage</strong>, vous seul (et les administrateurs de Kibana) pouvez le modifier ou le supprimer. Tous les autres peuvent l&#x27;ouvrir, l&#x27;utiliser et lui faire confiance, mais ils ne peuvent pas le modifier.</p><h3>Ce que vous obtenez</h3><ul><li><strong>Intégrité du tableau de bord</strong> : en mode <strong>affichage</strong>, les autres utilisateurs disposant d&#x27;un accès en modification dans l&#x27;espace ne peuvent ni modifier ni supprimer le tableau de bord. S&#x27;ils tentent de le faire, un message leur indique qu&#x27;il est verrouillé. Vos graphiques et votre logique restent intacts.</li><li><strong>Vous gardez le contrôle</strong> : vous êtes le propriétaire. Vous pouvez toujours modifier, affiner et mettre à jour. Le partage en lecture seule ne vous empêche pas d&#x27;accéder au contenu ; il verrouille la version visible par tous les autres utilisateurs.</li><li><strong>Cycle de vie flexible</strong> : vous pouvez à tout moment repasser un tableau de bord en mode &quot;modifiable&quot;. Et les administrateurs Kibana peuvent toujours gérer tous les tableaux de bord (par exemple, si le propriétaire quitte l&#x27;entreprise). Il n&#x27;y a pas d&#x27;impasse.</li></ul><p>Vous pouvez partager largement des tableaux de bord finalisés et stratégiques, en ayant l&#x27;assurance qu&#x27;ils resteront cohérents. Cette fonctionnalité est disponible dans <strong>tous les niveaux et offres Elastic</strong>, y compris Serverless.</p><h3>Qui peut faire quoi ?</h3><p>Référence rapide par rôle :</p><ul><li><strong>Propriétaire du tableau de bord</strong> : vous l&#x27;avez créé ; vous disposez d&#x27;un accès complet en modification.</li><li><strong>Administrateur Kibana</strong> : peut gérer tous les tableaux de bord.</li><li><strong>Utilisateur avec droit de modification dans l&#x27;espace</strong> : peut créer et modifier ses tableaux de bord ; ne peut ni modifier ni supprimer les tableaux de bord en mode lecture seule.</li><li><strong>Utilisateur avec droit d&#x27;affichage dans l&#x27;espace</strong> : peut uniquement consulter (et afficher) les tableaux de bord.</li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Action</td><td style="border: 1px solid #ccc; padding: 4px;">Propriétaire du tableau de bord</td><td style="border: 1px solid #ccc; padding: 4px;">Administrateur Kibana</td><td style="border: 1px solid #ccc; padding: 4px;">Utilisateur avec modification de l'espace</td><td style="border: 1px solid #ccc; padding: 4px;">Utilisateur avec droit d'affichage dans l'espace</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Afficher et consulter les tableaux de bord</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Créer de nouveaux tableaux de bord</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Modifier/supprimer les tableaux de bord modifiables</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Modifier ou supprimer les tableaux de bord en lecture seule</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td>
                    </tr>
              </tbody>
            </table>
          <h2>Comment activer le mode lecture seule</h2><p>Vous pouvez activer le mode lecture seule lors de l&#x27;enregistrement d&#x27;un nouveau tableau de bord ou ultérieurement depuis le menu Partager.</p><h3>Lors de l’enregistrement d’un nouveau tableau de bord</h3><ul><li>Créez votre tableau de bord, puis cliquez sur <strong>Enregistrer</strong>.</li><li>Dans la fenêtre modale &quot;Enregistrer en tant que nouveau tableau de bord&quot;, recherchez <strong>Autorisations</strong>.</li><li>Passez de <strong>Peut modifier</strong> à <strong>Peut afficher</strong>.</li><li>Cliquez sur <strong>Save</strong> (Enregistrer). Et le tour est joué ! C&#x27;est en lecture seule pour tous les autres utilisateurs.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42a71d1bb55f9d50bd079f53bf45a0e1999b27f7-1214x1306.png" style="width: 100%;" /><h2>Pour un tableau de bord que vous possédez déjà</h2><ul><li>Ouvrez le tableau de bord.</li><li>Ouvrez le menu <strong>Partager le tableau de bord</strong>.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e8405938557c879b1d4c262b98cf5a7f66408c04-1246x264.png" style="width: 100%;" /><ul><li>Dans la fenêtre de partage, recherchez <strong>Autorisations</strong> et sélectionnez <strong>Affichage uniquement</strong>. La modification s&#x27;applique immédiatement ; les autres utilisateurs dans l&#x27;espace ne peuvent plus le modifier ni le supprimer.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/849405bc32701f3ebe0def012d8ae3cf3813ea0a-996x750.png" style="width: 100%;" /><ul><li>Vous pouvez survoler l&#x27;action <strong>Partager</strong> avec la souris pour voir le type d&#x27;autorisations dont dispose un tableau de bord donné.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/80541ddb1b1bc567b0aeff693944ea8b6871d6a7-1270x320.png" style="width: 100%;" /><h3>Voir quels tableaux de bord sont verrouillés</h3><p>Dans la liste principale des tableaux de bord, les tableaux de bord que vous ne pouvez ni modifier ni supprimer sont signalés par une case à cocher désactivée. Cela permet de repérer facilement les tableaux de bord en lecture seule.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/24b2eba08df86174db949c662e7886c5aea1b460-1999x876.png" style="width: 100%;" /><p>Sur le tableau de bord, vous constaterez également que l&#x27;action Modifier est désactivée et qu&#x27;une info-bulle apparaît, expliquant que le tableau de bord a été configuré en lecture seule.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e0a2f9da6dc854e876fc6dc2a7c3ef8b313b52ef-1358x330.png" style="width: 100%;" /><h2>Faites l&#x27;essai</h2><p>Les tableaux de bord en lecture seule sont désormais disponibles. Créez un tableau de bord, passez-le en mode <strong>Affichage uniquement</strong> et partagez-le. Votre équipe dispose ainsi d&#x27;une source unique d&#x27;information fiable, et vous avez l&#x27;esprit tranquille. Fini les mentions &quot;Ne pas modifier&quot; dans le titre.</p><p>Nous aimerions savoir comment vous utilisez les tableaux de bord en lecture seule. Partagez vos commentaires sur notre <a href="https://discuss.elastic.co">forum communautaire</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[L'outil shell n'est pas une solution miracle pour l'ingénierie du contexte]]></title>
            <description><![CDATA[Découvrez quels outils de récupération de contexte existent pour l'ingénierie contextuelle, comment ils fonctionnent et leurs compromis.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/search-tools-context-engineering</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/search-tools-context-engineering</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Leonie Monigatti]]></dc:creator>
            <pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f91e4d07915ba7bf3b7abf15fac8fab3350f7df2-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Les outils les plus importants dont dispose un agent sont les outils de rechercher qu’il peut utiliser pour construire son propre contexte. Les récents articles de <a href="https://www.llamaindex.ai/blog/files-are-all-you-need">LlamaIndex</a> et <a href="https://x.com/hwchase17/status/2011814697889316930">LangChain</a> ont suscité une discussion : <em>un outil shell et un système de fichiers sont-ils tout ce dont un agent a besoin pour l’ingénierie du contexte ? </em>Malheureusement, la discussion a rapidement dévié sur un sujet inapproprié : système de fichiers contre base de données.</p><p>Ce billet se concentre sur la question <em>suivante : quelles sont les bonnes interfaces de recherche dont un agent a besoin pour construire son propre contexte ?</em> Il couvre d&#x27;abord les compromis entre les outils de shell et les outils de base de données dédiés. Il propose ensuite un framework pratique pour trouver les interfaces adaptées aux besoins de votre agent.</p><h2>Que signifie concrètement pour un agent le terme « contexte de construction » ?</h2><p>Dans les premiers <a href="https://www.elastic.co/what-is/retrieval-augmented-generation">pipelines de Retrieval-Augmented Generation (RAG)</a>, le développeur concevait un pipeline de recherche fixe, et le grand modèle de langage (LLM) était un récepteur passif du contexte. C&#x27;était une limitation fondamentale : le contexte était récupéré à chaque requête, qu&#x27;il soit nécessaire ou non, sans vérification qu&#x27;il aidait réellement.</p><p>Avec le passage au RAG agentique, les agents ont désormais accès à un ensemble d’outils de recherche pour construire leur propre contexte. Par exemple, Claude Code [1] et Cursor [2] permettent tous deux à l’agent de choisir entre différents outils de recherche et même de les combiner pour des requêtes chaînées, en fonction de ce que la tâche exige réellement.</p><h2>Quelles interfaces de recherche existent pour l&#x27;ingénierie contextuelle ?</h2><p>Le contexte peut se trouver à différents endroits, par exemple sur le Web, dans un système de fichiers local ou dans une base de données. Un agent peut interagir avec chacune de ces sources de données hors contexte à l&#x27;aide de différents outils :</p><ul><li><strong>Les outils Shell</strong> peuvent exécuter des commandes shell et accéder au système de fichiers local. Quelques exemples d’outils shell intégrés sont <a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/bash-tool">l’outil bash de Claude API</a>, <a href="https://docs.openclaw.ai/tools/exec">l’outil exécutif d’OpenClaw</a>, et <a href="https://docs.langchain.com/oss/python/integrations/tools/bash">l’outil shell de LangChain</a>.</li><li><strong>Les outils de base de données dédiés,</strong> tels que les outils d’un serveur Model Context Protocol (MCP) (par exemple, le <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">serveur MCP Elastic Agent Builder)</a> ou les outils personnalisés (par exemple, <code>run_esql(query)</code> ou <code>db_list_index()</code>), peuvent interroger les bases de données.</li><li><strong>Les outils de recherche de fichiers dédiés</strong> peuvent rechercher et lire des fichiers locaux (ou téléchargés) (sans accès complet au shell). Quelques exemples d&#x27;outils de recherche de fichiers intégrés sont <a href="https://ai.google.dev/gemini-api/docs/file-search">l&#x27;outil de recherche de fichiers de Gemini API</a> ou <a href="https://developers.openai.com/api/docs/guides/tools-file-search">l&#x27;outil de recherche de fichiers d&#x27;OpenAI</a>.</li><li><strong>Les outils de recherche Web</strong> peuvent extraire des informations du web.</li><li><strong>Les outils de mémoire</strong> stockent et rappellent la mémoire à long terme (quelle que soit la manière dont elle est stockée).</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/115f20c8ded259e508f51524b2c06bdc702d70ab-1999x1050.png" style="width: 100%;" /><p>Comme vous pouvez le voir, l’outil shell est polyvalent et peut être utilisé pour récupérer du contexte à partir de différentes sources de données, notamment :</p><ul><li><strong>Système de fichiers :</strong> l&#x27;agent explore la structure des répertoires (ls, find), recherche le contenu pertinent (grep, cat) et répète l&#x27;opération jusqu&#x27;à ce qu&#x27;il ait construit un contexte suffisant.</li><li><strong>Base de données :</strong> l’agent peut utiliser des outils d’interface en ligne de commande (CLI) de base de données (par exemple, <a href="https://www.elastic.co/docs/reference/query-languages/sql/sql-cli"><code>elasticsearch-sql-cli</code></a>), appeler des API HTTP via curl, ou exécuter des scripts, ce qui est particulièrement utile en combinaison avec les compétences de l’agent, qui sont des exemples réutilisables et documentés injectés dans le contexte de l’agent pour guider l’utilisation correcte des outils (par exemple, <a href="https://github.com/elastic/agent-skills">Elastic Agent Skills pour Elasticsearch</a>).</li><li><strong>Web : </strong>l’agent peut exécuter des recherches web via une commande curl à travers l’API d’un fournisseur de recherche.</li></ul><p>Cependant, l&#x27;outil shell fournit un accès système direct et nécessite donc des mesures de sécurité, telles que l&#x27;exécution dans un environnement sandbox isolé et le logging de toutes les commandes exécutées.</p><h2>Quand utiliser quelles interfaces de recherche</h2><p>L&#x27;interface de recherche appropriée dépend de vos données, de vos modèles de requête et de votre cas d&#x27;utilisation. Cette section constitue un point de départ pratique.</p><h3>Les systèmes de fichiers ne rendent pas les bases de données obsolètes</h3><p>Le débat entre systèmes de fichiers et bases de données ne porte pas sur la couche de stockage. Par exemple, LangChain explique que <a href="https://x.com/hwchase17/status/2011814697889316930">son système de mémoire</a> ne stocke pas réellement la mémoire dans un véritable système de fichiers. Au lieu de cela, il stocke la mémoire dans une base de données et la <em>représente</em> sous la forme d&#x27;un ensemble de fichiers pour l&#x27;agent [3].</p><p>Les systèmes de fichiers sont particulièrement adaptés aux cas d&#x27;utilisation natifs basés sur les fichiers, tels que les agents de codage. Ils fonctionnent également bien comme bloc-notes temporaire ou mémoire de travail pour les scénarios à utilisateur unique ou à agent unique où la concurrence n&#x27;est pas une préoccupation. Dans ces cas, un système de fichiers physique ou la représentation des données sous forme de système de fichiers vous offre une certaine flexibilité avant de vous engager dans une interface dédiée.</p><p>Mais le stockage par système de fichiers présente de réels inconvénients, tels qu&#x27;une faible concurrence, l&#x27;application manuelle du schéma et les transactions atomiques. Ces problèmes deviennent plus évidents lorsque votre application doit scaler ou passer à un scénario multi-agents. Quiconque ignore ces inconvénients est condamné à <a href="https://dx.tips/oops-database">réinventer péniblement des bases de données de moindre qualité</a>, sans bénéficier des décennies d&#x27;ingénierie qui sous-tendent la sécurité des transactions ou le contrôle d&#x27;accès que les bases de données de production offrent déjà. De plus, dans la plupart des contextes d&#x27;entreprise, on ne choisit pas d&#x27;utiliser ou non une base de données puisqu&#x27;elle est déjà en place et stocke des données essentielles à l&#x27;activité.</p><h3>Outil shell + système de fichiers</h3><p>Un outil shell est le point de départ naturel pour la recherche dans le système de fichiers. Actuellement, les agents de codage sont à l&#x27;origine de nombreux progrès dans le champ. Parce qu&#x27;ils travaillent avec du code dans des fichiers locaux, ce sont naturellement des cas d&#x27;utilisation gourmands en fichiers. Par conséquent, les LLM sont affinés lors de la phase de post-entraînement pour les tâches de codage. C&#x27;est pourquoi de nombreux LLM savent non seulement écrire du code, mais aussi utiliser des commandes shell et naviguer dans les systèmes de fichiers.</p><p>L&#x27;utilisation d&#x27;un outil shell avec des CLI intégrées, comme <code>ls</code> et <code>grep</code>, pour rechercher des fichiers est efficace. Avec grep, une requête comme « Trouver tous les fichiers qui importent <code>matplotlib</code>» est rapide, précise et peu coûteuse. Mais lorsque l&#x27;agent doit gérer des requêtes conceptuelles, comme « Comment notre application gère-t-elle une authentification défaillante ? », La correspondance de motifs avec grep peut rapidement atteindre ses limites. Plusieurs alternatives qui apportent des capacités de recherche sémantique à la ligne de commande ont émergé pour combler ce manque, notamment <a href="https://github.com/jina-ai/jina-grep-cli"><code>jina-grep</code></a>.</p><p>Cependant, grep et plusieurs de ses alternatives de recherche sémantique fonctionnent en O(n) sur le corpus. Pour les cas d&#x27;utilisation sur des bases de code, cela peut convenir. Cependant, si vos données s&#x27;accumulent, la latence deviendra perceptible. Dans ce cas, un datastore indexé devient nécessaire pour assurer la maintenance des performances.</p><h3>Outil shell + base de données</h3><p>Une autre façon d&#x27;ajouter des capacités de recherche, telles que la recherche sémantique ou hybride, à vos données est de les stocker dans une base de données, comme le fait Cursor, par exemple. De plus, lorsque les données nécessitent des jointures relationnelles complexes ou des agrégations, une interface de base de données est non négociable.</p><p>Lorsque les données se trouvent dans une base de données plutôt que dans le système de fichiers, un outil shell peut servir d&#x27;interface de base de données légère pour certains cas d&#x27;utilisation. Si vos requêtes sont assez simples pour une interface de ligne de commande ou un appel curl, un outil de base de données dédié peut ajouter de la complexité inutile.</p><p>Cette approche est également adaptée aux premières étapes de l’exploration, lorsque vous ne savez pas encore quels modèles de requête votre agent développera réellement. Dans ce cas, les compétences des agents peuvent donner à l’agent suffisamment de structure pour effectuer des requêtes correctement sans avoir recours à un outil spécialement conçu. Cependant, lorsque l&#x27;agent doit effectuer de nombreuses itérations pour déterminer la meilleure façon d&#x27;interroger la base de données pour des tâches répétitives, la surcharge de jetons associée à l&#x27;utilisation d&#x27;un outil shell comme interface ne justifie plus l&#x27;avantage de simplicité qu&#x27;offre l&#x27;évitement d&#x27;un outil supplémentaire.</p><h3>Outil de base de données dédié</h3><p>Des outils de base de données spécialisés deviennent nécessaires, surtout lorsque les modèles de requêtes répétées sont structurés ou analytiques. Un <a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">article de blog de Vercel et Braintrust</a> a comparé des agents utilisant différents ensembles d&#x27;outils de recherche pour des tâches de récupération réelles sur des données semi-structurées, telles que les tickets de service client et les transcriptions d&#x27;appels de vente (par exemple : « Combien de problèmes ouverts mentionnent la « sécurité » ? » ou « Trouver les problèmes où quelqu&#x27;un a signalé un bug et où quelqu&#x27;un a ensuite soumis une PR prétendant le corriger ? »). [4].</p><p>Les agents utilisant des outils de base de données dédiés consommaient moins de jetons, étaient plus rapides et faisaient moins d&#x27;erreurs que ceux utilisant uniquement un outil shell et un système de fichiers. La leçon à retenir est que les outils de base de données directs constituent le meilleur choix lorsque la requête exige un raisonnement analytique sur des données semi-structurées.</p><h3>Combinaison d&#x27;interfaces de recherche</h3><p>Aucune interface de recherche unique ne traite correctement toutes les requêtes. Par exemple, Cursor combine des outils shell (pour les recherches via grep) et des outils de recherche sémantique, et permet à l&#x27;agent de sélectionner l&#x27;outil approprié en fonction de la requête de l&#x27;utilisateur. Ils indiquent que l&#x27;agent choisit grep pour faire correspondre des symboles ou des chaînes spécifiques, la recherche sémantique pour les questions conceptuelles ou liées au comportement, et les deux pour les tâches exploratoires.</p><p>L&#x27;expérience Vercel rapporte la même chose : son agent hybride, ayant accès à la fois à un outil en ligne de commande (shell) et à un outil de base de données dédié, a obtenu les meilleures performances parmi tous les agents testés, en utilisant d&#x27;abord les outils de base de données dédiés, puis en vérifiant les résultats en parcourant le système de fichiers avec la commande « grep ». Cependant, cette approche utilise plus de tokens et de temps pour le choix et la vérification des outils.</p><p>Le schéma est identique dans les deux exemples : la composition Beats toute interface unique, mais elle implique un compromis en termes de coût et de latence supplémentaires.</p><h2>Recommandations pratiques pour trouver les bons outils</h2><p>Le bon ensemble d&#x27;interfaces de recherche est restreint, ciblé et spécifique aux schémas de requêtes réels de votre agent. La bonne pratique actuelle est d&#x27;avoir un agent avec le moins d&#x27;outils possible au lieu d&#x27;avoir un agent avec des centaines d&#x27;outils MCP. En effet, le fait d&#x27;exposer d&#x27;emblée tous les outils possibles a pour inconvénient de gonfler la fenêtre contextuelle et d&#x27;embrouiller l&#x27;agent quant à l&#x27;outil à utiliser. Par exemple, Claude Code ne disposerait que d&#x27;une vingtaine d&#x27;outils.</p><p>L&#x27;idée de la divulgation progressive est plutôt de commencer avec un ensemble minimal d&#x27;outils et de laisser l&#x27;agent découvrir des capacités supplémentaires uniquement lorsqu&#x27;il en a besoin. Les recherches menées par Anthropic [5] et Cursor [6] ont montré que cette approche permet de réaliser une économie de tokens de 47%–85%. Claude Code, par exemple, implémente cela directement, permettant à l&#x27;agent de découvrir progressivement comment interroger une API ou une base de données, sans que cette connaissance ne consomme du contexte à chaque appel de LLM.</p><p>Une fois que vous vous êtes familiarisé avec les modèles de requête de l’agent, vous pouvez revoir l’ensemble des outils de recherche auxquels l’agent a accès par défaut. Une façon utile d&#x27;envisager ce compromis est le <a href="https://www.elastic.co/search-labs/blog/database-retrieval-tools-context-engineering#building-the-right-database-retrieval-tools-%5C(%E2%80%9Clow-floor,-high-ceiling%E2%80%9D%5C">principe « plancher bas, plafond haut » </a> pour décider quels outils doivent être retenus. Les outils à haut plafond ne limitent pas le potentiel de l’agent. Par exemple, un outil shell polyvalent permet à l’agent d’écrire des requêtes de base de données complètes, y compris celles ambiguës, mais au prix d’une surcharge de raisonnement, d’une latence plus élevée et d’une fiabilité moindre.</p><p>Les outils à plancher surbaissé sont à l&#x27;opposé. Ce sont des outils spécialisés qui répondent à des requêtes spécifiques et sont immédiatement accessibles à l&#x27;agent avec un minimum de frais de raisonnement, ce qui permet de réduire les coûts et d&#x27;accroître la fiabilité. Mais ils nécessitent un travail d’ingénierie préalable, ne peuvent pas couvrir toutes les requêtes possibles, et peuvent compliquer le choix du bon outil pour l’agent.</p><p>Pensez à chaque outil sur un spectre : les outils à seuil bas sont faciles à utiliser correctement par l&#x27;agent mais sont limités en portée. Les outils à haut potentiel sont polyvalents, mais nécessitent davantage de réflexion pour être utilisés efficacement.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6d1b973be4b0a0a25c99c74f02a47e98395a3f7-1200x630.png" style="width: 100%;" /><p>La plupart des agents ont besoin d&#x27;une combinaison de différents outils de recherche. Mais chaque outil doit mériter son ajout. Nous recommandons de commencer par un outil de recherche polyvalent (par exemple un outil <code>search_database()</code> ou un outil shell). Réutilisez ensuite les logs de commandes que vous conservez déjà à des fins de sécurité pour suivre ce que votre agent fait réellement, y compris les appels d’outils, les nouvelles tentatives et le nombre d’appels par requête utilisateur. Et, lorsque vous voyez un modèle de requête se répéter ou échouer, c&#x27;est le signal pour créer un outil spécialement conçu à cet effet.</p><h2>Résumé</h2><p>Le débat système de fichiers contre base de données détourne l&#x27;attention de la véritable question que les ingénieurs doivent se poser : <em>quelles sont les bonnes interfaces de recherche dont un agent a besoin pour construire son propre contexte ?</em> La réponse est, selon toute vraisemblance, <em>pas une seule</em>.</p><p>Un outil shell est un outil polyvalent pour interagir avec différentes sources hors contexte et constitue ainsi un bon point de départ. Mais il est moins efficace et précis pour les cas d&#x27;utilisation avec des requêtes analytiques structurées que les outils de base de données dédiés.</p><p>L&#x27;objectif est de trouver l&#x27;ensemble minimal d&#x27;outils de recherche qui gère bien les modèles de requêtes réels de votre agent. Commencez avec un outil shell, et consignez ce que fait réellement votre agent dans les logs. Lorsque vous constatez qu&#x27;un schéma de requête se répète et échoue, il est temps de concevoir des outils spécialisés.</p><h2>Références</h2><p>1. Thariq (Anthropic). <a href="https://x.com/trq212/status/2027463795355095314">Leçons tirées de la construction du code Claude : voir comme un agent</a> (2026).</p><p>2. Cursor : Documentation. <a href="https://cursor.com/docs/agent/tools/search">Recherche sémantique et agentique</a> (2026).</p><p>3. Harrison Chase (LangChain). <a href="https://x.com/hwchase17/status/2011814697889316930">Comment nous avons construit le système de mémoire d&#x27;Agent Builder</a> (2026).</p><p>4. Ankur Goyal (Braintrust) et Andrew Qu (Vercel). <a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">Tester si « bash est tout ce dont vous avez besoin »</a> (2026).</p><p>5. Anthropic. <a href="https://www.anthropic.com/engineering/advanced-tool-use">Présentation de l&#x27;utilisation d&#x27;outils avancés sur la plateforme de développement Claude</a> (2025).</p><p>6. Cursor. <a href="https://cursor.com/blog/dynamic-context-discovery">Découverte dynamique du contexte</a> (2026).</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Répliques Elasticsearch pour l'équilibrage de charge dans Serverless]]></title>
            <description><![CDATA[Découvrez comment Elastic Cloud Serverless ajuste automatiquement les répliques d'index en fonction de la charge de recherche, garantissant une performance optimale des requêtes sans configuration manuelle.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-replicas-load-balancing-serverless</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-replicas-load-balancing-serverless</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Andrei Dan]]></dc:creator>
            <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3c4c1e99b892f026b7aba098973593f8298e2ea6-1280x717.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans Elastic Cloud Serverless, le nombre de répliques de vos index est automatiquement ajusté en fonction de la charge de recherche, garantissant ainsi des performances optimales pour vos requêtes, sans aucune configuration manuelle. Dans cet article, nous expliquons comment les répliques sont mises à l&#x27;échelle, à quel moment le système en ajoute ou en supprime, et quelles sont les conséquences pour vos index.</p><h2>La soirée commence à s&#x27;animer</h2><p>Vous organisez une fête de pizza. Vous avez quelques amis qui vous aident à servir, chacun posté à différents endroits dans la pièce. Vous offrez une pizza à chaque ami, et ils commencent à distribuer des tranches aux clients affamés à leur arrivée.</p><p>Au début, tout se passe bien. Les invités arrivent au compte-gouttes, vos amis servent des parts de pizza, tout le monde est content. Mais bientôt, la nouvelle de vos pizzas au levain se répand. On n&#x27;arrête pas de sonner à la porte. Les invités affluent. Très vite, une foule se forme autour de l&#x27;un de vos amis, celui qui tient la pizza pepperoni que tout le monde semble vouloir.</p><p>Votre ami avec la pizza au pepperoni est débordé. Les clients attendent, deviennent impatients et une longue file d&#x27;attente s&#x27;est formée. Pendant ce temps, votre ami avec la pizza margherita reste debout et presque personne ne lui demande une part.</p><p>Que faire ?</p><p>Vous commandez encore deux pizzas pepperoni et vous en distribuez à d&#x27;autres amis. Maintenant, trois amis ont de la pizza pepperoni au lieu d&#x27;un seul. La foule se disperse, et, tout à coup, vous pouvez servir trois fois plus d&#x27;invités à la fois.</p><p>Certains éléments deviennent clairs à mesure que vous organisez des fêtes :</p><ul><li><strong>Les pizzas n&#x27;ont pas toutes le même succès.</strong> Certaines sont très demandées, d&#x27;autres moins. Inutile de prévoir des &quot;exemplaires&quot; supplémentaires de celles qui sont peu appréciées. Il vous faut davantage de celles qui attirent les foules.</li><li><strong>Commandez d&#x27;autres pizzas avant que la file d&#x27;attente ne devienne trop longue.</strong> Si vous attendez que votre ami soit complètement dépassé et que les invités partent mécontents, vous avez attendu trop longtemps. Mieux vaut commander une pizza supplémentaire quand vous voyez une foule se former.</li><li><strong>Ne jetez pas les pizzas trop vite.</strong> Ce n&#x27;est pas parce que la foule autour de la pizza pepperoni s&#x27;est clairsemée pendant cinq minutes que l&#x27;affluence est terminée. Peut-être sont-ils simplement en train de se resservir à boire, ou même de discuter entre eux (est-ce que ça se fait encore ?). Prévoyez des pizzas supplémentaires. Si l&#x27;accalmie se prolonge, vous pourrez les mettre de côté.</li><li><strong>Vous ne pouvez distribuer autant de pizzas que vous avez d&#x27;amis qui vous aident.</strong> Si vous n&#x27;avez que quatre amis pour vous aider, dix pizzas ne changeront rien au résultat. Seules quatre peuvent être servies à la fois. Adaptez le nombre de pizzas à vos serveurs disponibles.</li><li><strong>Quand un ami s&#x27;en va, prenez sa pizza.</strong> Si l&#x27;un de vos amis doit partir, prenez sa pizza immédiatement. Vous ne pouvez pas laisser les pizzas sans surveillance. Donnez-la à quelqu&#x27;un d&#x27;autre, ou mettez-la de côté.</li></ul><h2>Des pizzas aux répliques</h2><p>Faisons le lien avec Elasticsearch.</p><p>Dans notre analogie, les pizzas sont des répliques (copies de vos shards d&#x27;index), vos amis qui aident à servir sont des nœuds de recherche, les invités affamés sont des requêtes de recherche, et cette pizza très prisée autour de laquelle se presse la foule correspond à un index très sollicité (hot) avec une charge de recherche élevée.</p><p>Lorsque le trafic de recherche augmente sur un indice donné, nous créons des répliques supplémentaires et les distribuons sur vos nœuds de recherche. Toute réplique peut traiter n&#x27;importe quelle requête pour cet index, tout comme un ami tenant une pizza pepperoni peut en distribuer des parts. Plus de répliques signifie un débit plus élevé : trois répliques peuvent traiter trois fois plus de requêtes par seconde qu&#x27;une seule réplique.</p><h2>Mesurer la faim</h2><p>Avant de décider du nombre de pizzas à commander, nous devons connaître l&#x27;appétit de la foule.</p><p>Elasticsearch suit la <strong>charge de recherche</strong> pour chaque partition. Cet indicateur mesure l&#x27;activité de recherche gérée par une partition. Nous agrégeons ces données pour l&#x27;ensemble des partitions d&#x27;un index afin d&#x27;appréhender la demande de recherche totale.</p><p>Ce qui importe le plus, c&#x27;est la <strong>charge de recherche relative</strong> : quelle proportion du trafic de recherche total de votre projet est allouée à chaque index ? Si un index reçoit 60 % des recherches tandis qu&#x27;un autre n&#x27;en reçoit que 5 %, nous savons où augmenter la capacité.</p><h2>Les mathématiques derrière les pizzas</h2><p>Nous calculons le nombre optimal de répliques en suivant cette formule :</p><pre><code>desired_replicas = min(ceil(L × N / (S × X)), N)</code></pre><p>Où :</p><ul><li><strong>L</strong> = la charge de recherche relative de l&#x27;index (entre 0 et 1).</li><li><strong>N</strong> = le nombre de nœuds de recherche souhaités dans votre projet.</li><li><strong>S</strong> = le nombre de partitions dans l&#x27;index.</li><li><strong>X</strong> = un seuil pour éviter les points chauds (0,5 par défaut).</li></ul><p>Un exemple : quatre nœuds de recherche, un index avec deux shards primaires recevant 80 % du trafic de recherche :</p><pre><code>desired_replicas = min(ceil(0.8 × 4 / (2 × 0.5)), 4)
                 = min(4, 4)
                 = 4</code></pre><p>Cet index hot obtient quatre répliques réparties sur les nœuds de recherche.</p><p>Le seuil X (0,5 par défaut) est important. Nous n&#x27;attendons pas qu&#x27;une réplique soit complètement saturée ; nous augmentons la capacité lorsqu&#x27;elle en est à la moitié. Distribuez les pizzas supplémentaires dès que vous voyez le monde arriver, et non lorsque les clients sont déjà partis.</p><h2>Augmenter la capacité rapidement, la réduire lentement</h2><p>Lorsque la charge de recherche augmente, nous ajoutons immédiatement des répliques. Inutile de faire attendre les utilisateurs.</p><p>Lorsque la charge de recherche diminue, nous attendons un peu avant d&#x27;agir. Nous devons observer une faible demande stable pendant environ 30 minutes avant de réduire le nombre de répliques. (Ceci afin de gérer les pics de trafic, où une période d&#x27;accalmie ne signifie pas que la charge est terminée.)</p><p>C&#x27;est important, car l&#x27;ajout d&#x27;une réplique a un coût. La nouvelle réplique copie les données et initialise ses caches avant de traiter efficacement les requêtes. Supprimer des répliques trop rapidement signifie payer constamment ce coût initial, car le trafic fluctue naturellement.</p><h2>Respecter les limites de la topologie</h2><p>Le nombre de répliques ne peut jamais dépasser le nombre de nœuds de recherche. Avoir plus de répliques que de nœuds n&#x27;apporte aucun avantage (vous ne pouvez servir qu&#x27;autant de pizzas que vous avez d&#x27;amis qui vous aident à en servir des parts).</p><p>Lorsque des nœuds sont retirés de votre projet, nous réduisons immédiatement le nombre de répliques en conséquence. Pas besoin d&#x27;attendre la fin du cooldown, car il ne peut y avoir de répliques non attribuées. Dès qu&#x27;un ami s&#x27;en va, nous retirons sa part.</p><h2>Une vision plus globale du Serverless</h2><p>Les répliques pour l&#x27;équilibrage de charge de recherche fonctionnent de pair avec d&#x27;autres systèmes d&#x27;autoscaling :</p><ul><li>L&#x27;<strong>autoscaling de la recherche</strong> ajuste le nombre de nœuds de recherche (combien d&#x27;amis aident).</li><li>Les <strong>répliques pour l&#x27;équilibrage de la charge de recherche</strong> distribuent le trafic en ajustant le nombre de répliques par index (le nombre de pizzas de chaque type dont nous avons besoin).</li><li>Le <strong>partitionnement automatique du flux de données</strong> optimise le nombre de partitions pour les écritures (comment découper chaque pizza, abordé dans l&#x27;<a href="https://www.elastic.co/search-labs/blog/datastream-autosharding-serverless">article précédent</a>).</li></ul><p>Un principe de conception important : les répliques pour l&#x27;équilibrage de charge ne déclenchent pas directement l&#x27;autoscaling de la recherche. En répartissant les requêtes de recherche sur un plus grand nombre de répliques, nous pouvons augmenter l&#x27;utilisation des ressources sur l&#x27;ensemble de vos nœuds de recherche. Cette utilisation accrue active ensuite notre logique d&#x27;autoscaling existante afin d&#x27;ajouter de la capacité si nécessaire. Les répliques pour l&#x27;équilibrage de charge permettent à l&#x27;autoscaling de remplir sa fonction, en garantissant que vos nœuds de recherche sont effectivement utilisés, au lieu de voir tout le trafic se concentrer sur une seule réplique tandis que les autres nœuds restent inactifs.</p><h2>Ce que cela signifie pour vous</h2><p>Vous n&#x27;avez pas besoin de prédire quels index seront les plus sollicités. Vous n&#x27;avez pas besoin d&#x27;ajuster manuellement les répliques lorsque les schémas de trafic changent. Vous n&#x27;avez pas besoin de vous réveiller à 3 heures du matin parce qu&#x27;un pic de trafic a saturé votre index le plus sollicité.</p><p>Le système surveille les zones d&#x27;affluence et commande des pizzas supplémentaires pour ces zones. Les index peu sollicités ne gaspillent pas de ressources en répliques inutiles. Les index très sollicités obtiennent la capacité dont ils ont besoin. Votre budget est ainsi investi là où c&#x27;est le plus important.</p><h2>Conclusion</h2><p>Dans l&#x27;<a href="https://www.elastic.co/search-labs/blog/datastream-autosharding-serverless">article sur le partitionnement automatique</a>, nous avons veillé à ce que vos pizzas soient découpées correctement. Désormais, grâce aux répliques pour l&#x27;équilibrage de charge de recherche, nous nous assurons que vous ayez suffisamment de pizzas, entre de bonnes mains, lorsque la foule affamée arrive.</p><p>Essayez <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a> et laissez-nous nous occuper la logistique des pizzas.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Utilisation de l'API d'inférence Elasticsearch avec les modèles Hugging Face]]></title>
            <description><![CDATA[Découvrez comment connecter Elasticsearch aux modèles Hugging Face à l'aide de points de terminaison d'inférence, et comment créer un système de recommandation de blogs multilingue avec recherche sémantique et complétion de chat.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/hugging-face-elasticsearch-inference-api</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/hugging-face-elasticsearch-inference-api</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1417d6ff033712206c9bd4bcc22074ee3437ce96-1999x1125.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans ses dernières mises à jour, Elasticsearch a introduit une intégration native permettant de se connecter aux modèles hébergés sur le <a href="https://endpoints.huggingface.co/">service d&#x27;inférence Hugging Face</a>. Dans cet article, nous verrons comment configurer cette intégration et effectuer des inférences via de simples appels d&#x27;API à l&#x27;aide d&#x27;un grand modèle de langage (LLM). Nous utiliserons <a href="https://huggingface.co/HuggingFaceTB/SmolLM3-3B">SmolLM3-3B</a>, un modèle léger et polyvalent offrant un bon compromis entre consommation de ressources et qualité des réponses.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7ddadf1976421a860a7d62087239adb9150d808b-1999x1388.png" style="width: 100%;" /><h2>Produits requis</h2><ul><li><strong>Elasticsearch 9.3 ou Elastic Cloud Serverless</strong> : vous pouvez créer un déploiement dans le cloud en suivant <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">ces instructions</a>, ou utiliser le démarrage rapide <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart#local-dev-quick-start"><code>start-local</code></a> à la place.</li><li><strong>Python 3.12</strong> : téléchargez <a href="https://www.python.org/">Python ici</a>.</li><li><strong>Jeton d&#x27;accès Hugging Face</strong><a href="https://huggingface.co/docs/hub/en/security-tokens"></a>.</li></ul><h2>Complétions de chat utilisant un point de terminaison d&#x27;inférence Hugging Face</h2><p>Nous allons d&#x27;abord créer un exemple pratique connectant Elasticsearch à un <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put">point de terminaison d&#x27;inférence</a> Hugging Face afin de générer des recommandations alimentées par l&#x27;IA à partir d&#x27;une collection d&#x27;articles de blog. Pour la base de connaissances de l&#x27;application, nous utiliserons un ensemble de données d&#x27;articles de blogs d&#x27;entreprise, qui contiennent des informations précieuses mais souvent difficiles à consulter.</p><p>Avec ce point de terminaison, la <a href="https://www.elastic.co/docs/solutions/search/semantic-search">recherche sémantique</a> extrait les articles les plus pertinents pour une requête donnée, et un LLM Hugging Face génère de courtes recommandations contextuelles sur la base de ces résultats.</p><p>Examinons d&#x27;abord les grandes lignes du flux d&#x27;informations que nous allons mettre en place :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1dfbc2323438feaaa42e13ab242dd1f7166f74aa-1200x676.png" style="width: 100%;" /><p>Dans cet article, nous allons tester la capacité de <strong>SmolLM3-3B </strong>à<strong> </strong>à allier sa taille compacte à de puissantes fonctionnalités de raisonnement multilingue et d&#x27;appel d&#x27;outils. À partir d&#x27;une requête de recherche, nous enverrons tous les contenus correspondants (en anglais et en espagnol) au LLM afin de générer une liste d&#x27;articles recommandés, accompagnés d&#x27;une description personnalisée basée sur la requête et les résultats de recherche.</p><p>Voici à quoi pourrait ressembler l&#x27;interface utilisateur d&#x27;un site d&#x27;articles doté d&#x27;un système de génération de recommandations par IA.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8d3b86b212f28ff279f2da67a33e6134039f0e4e-1999x949.png" style="width: 100%;" /><p>Vous trouverez la mise en œuvre complète de cette application dans le <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/notebook.ipynb">notebook</a> associé.</p><h3>Configuration des points de terminaison d’inférence Elasticsearch</h3><p>Pour utiliser le <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-hugging-face">point de terminaison d&#x27;inférence Hugging Face d&#x27;Elasticsearch</a>, nous avons besoin de deux éléments importants : une clé API Hugging Face et une URL de point de terminaison Hugging Face en cours d&#x27;exécution. Cela devrait ressembler à ceci :</p><pre><code>PUT _inference/chat_completions/hugging-face-smollm3-3b
{
    "service": "hugging_face",
    "service_settings": {
        "api_key": "hugging-face-access-token", 
        "url": "url-endpoint" 
    }
}</code></pre><p>Le point de terminaison d&#x27;inférence Hugging Face dans Elasticsearch prend en charge différents types de tâches : <code>text_embedding</code>, <code>completion</code>, <code>chat_completion</code> et <code>rerank</code>. Dans cet article de blog, nous utilisons <code>chat_completion</code>, car nous avons besoin que le modèle génère des recommandations conversationnelles basées sur les résultats de recherche et un prompt système. Ce point de terminaison nous permet d&#x27;effectuer des complétions de chat directement depuis Elasticsearch de manière simple grâce à l&#x27;API Elasticsearch :</p><pre><code>POST _inference/chat_completion/hugging-face-smollm3-3b/_stream
{
  "messages": [
      { "role": "user", "content": "<user prompt>" }
  ]
}</code></pre><p>Ceci va constituer le cœur de l&#x27;application, recevant la requête et les résultats de recherche qui seront ensuite traités par le modèle. La théorie étant posée, passons à la mise en œuvre de l&#x27;application.</p><h4>Configuration du point de terminaison d&#x27;inférence sur Hugging Face</h4><p>Pour déployer le modèle Hugging Face, nous allons utiliser le <a href="https://huggingface.co/inference-endpoints/dedicated">service de déploiement en un clic de Hugging Face</a>, une solution simple et rapide pour déployer des points de terminaison de modèles. Notez qu&#x27;il s&#x27;agit d&#x27;un service payant et que son utilisation peut engendrer des coûts supplémentaires. Cette étape créera l&#x27;instance du modèle qui servira à générer les recommandations d&#x27;articles.</p><p>Vous pouvez choisir un modèle dans le catalogue accessible en un clic :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b816e9fba1fe172687bf58f5143fb1f838c1077f-549x331.png" style="width: 100%;" /><p>Sélectionnons le modèle <strong>SmolLM3-3B</strong> :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/610d3aba0429f3666c2df3616d513eb6a4397c0c-502x478.png" style="width: 100%;" /><p>À partir d&#x27;ici, veuillez récupérer l&#x27;URL du point de terminaison Hugging Face :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/025094ddb2cfbd1f0f216a5ec4e119b0f4fa2c42-646x328.png" style="width: 100%;" /><p>Comme indiqué dans la <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-hugging-face">documentation Elasticsearch relative aux points de terminaison d&#x27;inférence Hugging Face</a>, la génération de texte nécessite un modèle compatible avec l&#x27;API OpenAI. Pour cette raison, nous devons ajouter le sous-chemin <code>/v1/chat/completions</code> à à l&#x27;URL de point de terminaison Hugging Face. Le résultat final ressemblera à ceci :</p><pre><code>https://j2g31h0futopfkli.us-east-1.aws.endpoints.huggingface.cloud/v1/chat/completions</code></pre><p>Une fois ces éléments en place, nous pouvons commencer à coder dans un notebook Python.</p><h4>Génération de la clé API Hugging Face</h4><p>Créez un <a href="https://huggingface.co/join">compte Hugging Face</a> et obtenez un jeton API en suivant <a href="https://huggingface.co/docs/hub/en/security-tokens#user-access-tokens">ces instructions</a>. Vous avez le choix entre trois types de jetons : un jeton <em>granulaire</em> (recommandé pour la production, car il ne donne accès qu&#x27;à des ressources spécifiques), un jeton de <em>lecture</em> (pour un accès en lecture seule) ou un jeton d&#x27;<em>écriture</em> (pour un accès en lecture et en écriture). Pour ce tutoriel, un jeton de lecture suffit, car nous n&#x27;avons besoin d&#x27;appeler que le point de terminaison d&#x27;inférence. Enregistrez cette clé pour la prochaine étape.</p><h4>Configuration du point de terminaison d&#x27;inférence Elasticsearch</h4><p>Tout d&#x27;abord, déclarons un client Elasticsearch Python :</p><pre><code>os.environ["ELASTICSEARCH_API_KEY"] = "your-elasticsearch-api-key"
os.environ["ELASTICSEARCH_URL"] = "https://xxxx.us-central1.gcp.cloud.es.io:443"

es_client = Elasticsearch(
    os.environ["ELASTICSEARCH_URL"], api_key=os.environ["ELASTICSEARCH_API_KEY"]
)</code></pre><p>Ensuite, nous allons créer un point de terminaison d&#x27;inférence Elasticsearch qui utilise le modèle Hugging Face. Ce point de terminaison nous permettra de générer des réponses en fonction des articles de blog et du prompt transmis au modèle.</p><pre><code>INFERENCE_ENDPOINT_ID = "smollm3-3b-pnz"

os.environ["HUGGING_FACE_INFERENCE_ENDPOINT_URL"] = (
 "https://j2g31h0futopfkli.us-east-1.aws.endpoints.huggingface.cloud/v1/chat/completions"
)
os.environ["HUGGING_FACE_API_KEY"] = "hf_xxxxx"

resp = es_client.inference.put(
        task_type="chat_completion",
        inference_id=INFERENCE_ENDPOINT_ID,
        body={
            "service": "hugging_face",
            "service_settings": {
                "api_key": os.environ["HUGGING_FACE_API_KEY"],
                "url": os.environ["HUGGING_FACE_INFERENCE_ENDPOINT_URL"],
            },
        },
    )</code></pre><h3>Ensemble de données</h3><p>L&#x27;ensemble de données contient les <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/dataset.json">articles de blog</a> sur lesquels des requêtes seront exécutées ; il s&#x27;agit d&#x27;un ensemble de contenus multilingues utilisé tout au long du workflow :</p><pre><code>// Articles dataset document example: 
{
    "id": "6",
    "title": "Complete guide to the new API: Endpoints and examples",
    "author": "Tomas Hernandez",
    "date": "2025-11-06",
    "category": "tutorial",
    "content": "This guide describes in detail all endpoints of the new API v2. It includes code examples in Python, JavaScript, and cURL for each endpoint. We cover authentication, resource creation, queries, updates, and deletion. We also explain error handling, rate limiting, and best practices. Complete documentation is available on our developer portal."
  }</code></pre><h4>Mappings Elasticsearch</h4><p>Une fois l&#x27;ensemble de données défini, nous devons créer un schéma de données adapté à la structure des articles de blog. Les <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">mappings d&#x27;index</a> suivants seront utilisés pour stocker les données dans Elasticsearch :</p><pre><code>INDEX_NAME = "blog-posts"

mapping = {
    "mappings": {
        "properties": {
            "id": {"type": "keyword"},
            "title": {
                "type": "object",
                "properties": {
                    "original": {
                        "type": "text",
                        "copy_to": "semantic_field",
                        "fields": {"keyword": {"type": "keyword"}},
                    },
                    "translated_title": {
                        "type": "text",
                        "fields": {"keyword": {"type": "keyword"}},
                    },
                },
            },
            "author": {"type": "keyword", "copy_to": "semantic_field"},
            "category": {"type": "keyword", "copy_to": "semantic_field"},
            "content": {"type": "text", "copy_to": "semantic_field"},
            "date": {"type": "date"},
            "semantic_field": {"type": "semantic_text"},
        }
    }
}


es_client.indices.create(index=INDEX_NAME, body=mapping)</code></pre><p>Ici, nous pouvons voir plus clairement comment les données sont structurées. Nous utiliserons la recherche sémantique pour récupérer les résultats basés sur le langage naturel, ainsi que la propriété <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a> pour copier le contenu du champ dans le champ <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_text</code></a>. De plus, le champ <code>title</code> contient deux sous-champs : le sous-champ <code>original</code> stocke le titre en anglais ou en espagnol, selon la langue d&#x27;origine de l&#x27;article, et le sous-champ <code>translated_title</code> n&#x27;est présent que pour les articles en espagnol et contient la traduction anglaise du titre original.</p><h3>Ingestion des données</h3><p>L&#x27;extrait de code suivant ingère l&#x27;ensemble de données des articles de blog dans Elasticsearch à l&#x27;aide de l&#x27;<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript/bulk_examples">API Bulk</a> :</p><pre><code>def build_data(json_file, index_name):
    with open(json_file, "r") as f:
        data = json.load(f)

    for doc in data:
        action = {"_index": index_name, "_source": doc}
        yield action


try:
    success, failed = helpers.bulk(
        es_client,
        build_data("dataset.json", INDEX_NAME),
    )
    print(f"{success} documents indexed successfully")

    if failed:
        print(f"Errors: {failed}")
except Exception as e:
    print(f"Error: {str(e)}")</code></pre><p>Maintenant que nous avons intégré les articles dans Elasticsearch, nous devons créer une fonction capable de rechercher dans le champ <code>semantic_text</code> :</p><pre><code>def perform_semantic_search(query_text, index_name=INDEX_NAME, size=5):
    try:
        query = {
            "query": {
                "match": {
                    "semantic_field": {
                        "query": query_text,
                    }
                }
            },
            "size": size,
        }

        response = es_client.search(index=index_name, body=query)
        hits = response["hits"]["hits"]

        return hits
    except Exception as e:
        print(f"Semantic search error: {str(e)}")
        return []</code></pre><p>Nous avons également besoin d&#x27;une fonction qui appelle le point de terminaison d&#x27;inférence. Dans ce cas, nous appellerons le point de terminaison en utilisant le type de tâche <strong><code>chat_completion</code> </strong>pour obtenir des réponses en streaming :</p><pre><code>def stream_chat_completion(messages: list, inference_id: str = INFERENCE_ENDPOINT_ID):
    url = f"{ELASTICSEARCH_URL}/_inference/chat_completion/{inference_id}/_stream"
    payload = {"messages": messages}
    headers = {
        "Authorization": f"ApiKey {ELASTICSEARCH_API_KEY}",
        "Content-Type": "application/json",
    }

    try:
        response = requests.post(url, json=payload, headers=headers, stream=True)
        response.raise_for_status()

        for line in response.iter_lines(decode_unicode=True):
            if line:
                line = line.strip()

                if line.startswith("event:"):
                    continue

                if line.startswith("data: "):
                    data_content = line[6:]

                    if not data_content.strip() or data_content.strip() == "[DONE]":
                        continue

                    try:
                        chunk_data = json.loads(data_content)

                        if "choices" in chunk_data and len(chunk_data["choices"]) > 0:
                            choice = chunk_data["choices"][0]
                            if "delta" in choice and "content" in choice["delta"]:
                                content = choice["delta"]["content"]
                                if content:
                                    yield content

                    except json.JSONDecodeError as json_err:
                        print(f"\nJSON decode error: {json_err}")
                        print(f"Problematic data: {data_content}")
                        continue

    except requests.exceptions.RequestException as e:
        yield f"Error: {str(e)}"</code></pre><p>Nous pouvons maintenant écrire une fonction qui appelle la fonction de recherche sémantique, ainsi que le point de terminaison d&#x27;inférence <code>chat_completions</code> et le point de terminaison de recommandations, afin de générer les données qui seront allouées dans les fiches :</p><pre><code>def recommend_articles(search_query, index_name=INDEX_NAME, max_articles=5):
    print(f"\n{'='*80}")
    print(f"🔍 Search Query: {search_query}")
    print(f"{'='*80}\n")

    articles = perform_semantic_search(search_query, index_name, size=max_articles)

    if not articles:
        print("❌ No relevant articles found.")
        return None, None

    print(f"✅ Found {len(articles)} relevant articles\n")

    # Build context with found articles
    context = "Available blog articles:\n\n"
    for i, article in enumerate(articles, 1):
        source = article.get("_source", article)
        context += f"Article {i}:\n"
        context += f"- Title: {source.get('title', 'N/A')}\n"
        context += f"- Author: {source.get('author', 'N/A')}\n"
        context += f"- Category: {source.get('category', 'N/A')}\n"
        context += f"- Date: {source.get('date', 'N/A')}\n"
        context += f"- Content: {source.get('content', 'N/A')}\n\n"

    system_prompt = """You are an expert content curator that recommends blog articles.

    Write recommendations in a conversational style starting with phrases like:
    - "If you're interested in [topic], this article..."
    - "This post complements your search with..."
    - "For those looking into [topic], this article provides..."


    FORMAT REQUIREMENTS:
    - Return ONLY a JSON array
    - Each element must have EXACTLY these three fields: "article_number", "title", "recommendation"
    - If the original title is in spanish, use the "translated_title" subfield in the "title" field

    Keep each recommendation concise (2-3 sentences max) and focused on VALUE to the reader.

    EXAMPLE OF CORRECT FORMAT:
    [
        {"article_number": 1, "title": "Article title in english", "recommendation": "If you are interested in [topic], this article provides..."},
        {"article_number": 2, "title": "Article title in english", "recommendation": " for those looking into [topic], this article provides..."}
    ]

    Return ONLY the JSON array following this exact structure."""

    user_prompt = f"""Search query: "{search_query}"

    Generate recommendations for the following articles: {context}
    """

    messages = [
        {"role": "system", "content": "/no_think"},
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt},
    ]

    # LLM generation
    print(f"{'='*80}")
    print("🤖 Generating personalized recommendations...\n")

    full_response = ""

    for chunk in stream_chat_completion(messages):
        print(chunk, end="", flush=True)
        full_response += chunk

    return context, articles, full_response</code></pre><p>Enfin, nous devons extraire les informations et les mettre en forme pour l&#x27;impression :</p><pre><code>def display_recommendation_cards(articles, recommendations_text):
    print("\n" + "=" * 100)
    print("📇 RECOMMENDED ARTICLES".center(100))
    print("=" * 100 + "\n")

    # Parse JSON recommendations - clean tags and extract JSON
    recommendations_list = []
    try:

        # Clean up <think> tags
        cleaned_text = re.sub(
            r"<think>.*?</think>", "", recommendations_text, flags=re.DOTALL
        )
        # Remove markdown code blocks ( ... ``` or ``` ... ```)
        cleaned_text = re.sub(r"```(?:json)?", "", cleaned_text)
        cleaned_text = cleaned_text.strip()

        parsed = json.loads(cleaned_text)

        # Extract recommendations from list format
        for item in parsed:
            article_number = item.get("article_number")
            title = item.get("title", "")
            rec_text = item.get("recommendation", "")

            if article_number and rec_text:
                recommendations_list.append(
                    {
                        "article_number": article_number,
                        "title": title,
                        "recommendation": rec_text,
                    }
                )
    except json.JSONDecodeError as e:
        print(f"⚠️  Could not parse recommendations as JSON: {e}")
        return

    for i, article in enumerate(articles, 1):
        source = article.get("_source", article)

        # Card border
        print("┌" + "─" * 98 + "┐")

        # Find recommendation and title for this article number
        recommendation = None
        title = None
        for rec in recommendations_list:
            if rec.get("article_number") == i:
                recommendation = rec.get("recommendation")
                title = rec.get("title")
                break

        # Print title
        title_lines = textwrap.wrap(f"📌 {title}", width=94)
        for line in title_lines:
            print(f"│  {line}".ljust(99) + "│")

        # Card border
        print("├" + "─" * 98 + "┤")

        # Print recommendation
        if recommendation:
            recommendation_lines = textwrap.wrap(recommendation, width=94)
            for line in recommendation_lines:
                print(f"│  {line}".ljust(99) + "│")

        # Card bottom
        print("└" + "─" * 98 + "┘")</code></pre><p>Faisons un test en posant une question sur les articles de blog relatifs à la sécurité :</p><pre><code>search_query = "Security and vulnerabilities"

context, articles, recommendations = recommend_articles(search_query)

print("\nElasticsearch context:\n", context)

# Display visual cards
display_recommendation_cards(articles, recommendations)</code></pre><p>Nous pouvons voir ici les fiches générées par le workflow dans la console :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/730d35212594bb3db30447c3ea7e2a92857287b7-1999x1515.png" style="width: 100%;" /><p>Vous trouverez les résultats complets, y compris tous les résultats et la réponse du LLM dans <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/results.md">ce fichier</a>.</p><p>Nous recherchons des articles portant sur le thème &quot;Security et vulnérabilités&quot;. Cette question est utilisée comme requête de recherche sur les documents stockés dans Elasticsearch. Les résultats récupérés sont ensuite transmis au modèle, qui génère des recommandations basées sur leur contenu. Comme nous pouvons le constater, le modèle a parfaitement réussi à générer des textes courts et attrayants qui incitent le lecteur à cliquer dessus.</p><h2>Conclusion</h2><p>Cet exemple illustre comment combiner Elasticsearch et Hugging Face pour créer un système centralisé, rapide et performant pour les applications d&#x27;IA. Cette approche réduit les interventions manuelles et offre une grande flexibilité grâce au vaste catalogue de modèles de Hugging Face. L&#x27;utilisation de SmolLM3-3B, en particulier, montre comment des modèles multilingues compacts peuvent fournir un raisonnement pertinent et une génération de contenu efficace lorsqu&#x27;ils sont associés à la recherche sémantique. Ensemble, ces outils constituent une base scalable et performante pour le développement d&#x27;applications d&#x27;analyse de contenu intelligentes et multilingues.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/recall-vector-search-quantization</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/recall-vector-search-quantization</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
            <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/09f03b9239d66c36763cdab3fafcdac207ff6d83-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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&#x27;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&#x27;optimisation principales :</p><ol><li><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.</li><li><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&#x27;utilisation de la mémoire et d&#x27;accélérer les calculs.</li></ol><p>Mais l&#x27;optimisation s&#x27;accompagne souvent d&#x27;une taxe : la <strong>précision</strong>.</p><p>La crainte est légitime : &quot;Si je compresse mes données et que je prends des raccourcis pendant la recherche, vais-je manquer les meilleurs résultats ?&quot; &quot;Cette optimisation dégrade-t-elle la pertinence de mon moteur de recherche ?&quot;</p><p>Pour prouver que la quantification d&#x27;Elastic ne dégrade pas les résultats, nous avons construit un banc d&#x27;essai reproductible utilisant l&#x27;ensemble de données <a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"><strong>DBPedia-14</strong></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&#x27;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&#x27;examiner le code, clarifions certains termes.</p><ul><li><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).</li><li><strong>Recherche exacte (à plat)</strong> : parfois appelée méthode &quot;force brute&quot;. Le moteur de recherche analyse chaque document dans un index et calcule la distance.<ul><li><em>Avantages</em> : rappel parfait à 100 %.</li><li><em>Inconvénients</em> : coût de calcul élevé et lenteur à grande échelle.</li></ul></li><li><strong>Recherche approximative (HNSW)</strong> : la méthode &quot;raccourci&quot;. 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.<ul><li><em>Avantages</em> : extrêmement rapide et scalable.</li><li><em>Inconvénients</em> : vous risquez de manquer un voisin si le parcours du graphe s&#x27;arrête trop tôt.</li></ul></li></ul><h2><strong>L&#x27;expérience : exact versus approximatif</strong></h2><p>Pour tester le rappel, nous avons utilisé l&#x27;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&#x27;entraînement et l&#x27;évaluation des modèles de catégorisation de texte. Plus précisément, nous nous concentrerons sur la catégorie &quot;Film&quot;. 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&#x27;excellente précision de Jina v5 avec la quantification native d&#x27;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><strong><code>content.raw</code> </strong>avec le type : <code>flat</code>. Cela force Elasticsearch à effectuer une analyse exhaustive de l&#x27;ensemble des vecteurs Float32. Cette analyse renvoie des résultats de correspondance exacte qui serviront de référence.</li><li><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&#x27;agit du paramétrage standard optimisé pour la production, permettant une correspondance approximative.</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>Si la recherche <strong>exacte (à plat)</strong> indique que les 10 premiers voisins sont les ID [1, 2, 3... 10].</li><li>Et que la recherche <strong>approximative (HNSW)</strong> renvoie les identifiants [1, 2, 3… 9, 99].</li><li>Nous avons trouvé neuf des 10 premiers correctement. Le score est <strong>0,9</strong>.</li></ul><p>Voici le mapping que nous avons utilisé :</p><pre><code># 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"
        }
    }
}</code></pre><p><strong>Les résultats : la &quot;ligne plate&quot; du succès</strong></p><p>Nous avons effectué un test d&#x27;échelle, en rechargeant l&#x27;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>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Documents</td><td style="border: 1px solid #ccc; padding: 4px;">Score de rappel à 10</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">1 000</td><td style="border: 1px solid #ccc; padding: 4px;">1,000 (100 %)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">5 000</td><td style="border: 1px solid #ccc; padding: 4px;">0,998 (100 %)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10 000</td><td style="border: 1px solid #ccc; padding: 4px;">0,992 (99,4 %)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">20,000</td><td style="border: 1px solid #ccc; padding: 4px;">0,999 (99,0 %)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">40 000</td><td style="border: 1px solid #ccc; padding: 4px;">0,992 (98,8 %)</td>
                    </tr>
              </tbody>
            </table>
          <p>Les résultats étaient incroyablement stables. Même en augmentant l&#x27;échelle, la recherche approximative correspondait à la recherche exacte par force brute <strong>&gt;99 % du temps</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4bfacb1d0cce6fdf6df0e1a9d4fc5d4007a66da-1999x1209.png" style="width: 100%;" /><h2><strong>Pourquoi cela a-t-il si bien fonctionné ?</strong></h2><p>On pourrait s&#x27;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><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.</li><li><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.</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&#x27;il est possible d&#x27;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&#x27;en déterminer les limites.</p><h2><strong>Conclusion</strong></h2><p>Il s&#x27;agit d&#x27;un test à très petite échelle. L&#x27;objectif de cet exercice n&#x27;est pas de mesurer le modèle d&#x27;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>
        </item>
        <item>
            <title><![CDATA[L'extension Gemini CLI pour Elasticsearch avec des outils et des fonctionnalités]]></title>
            <description><![CDATA[Présentation de l’extension Elastic pour le CLI Gemini de Google, afin de rechercher, récupérer et analyser les données Elasticsearch dans les workflows des développeurs et des agents.
]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/gemini-cli-extension-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/gemini-cli-extension-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Walter Rafelsberger, Enrico Zimuel]]></dc:creator>
            <pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6dfcec4a77b3dc83bf0d974417bf2e211abb1f4f-876x468.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Nous sommes heureux d&#x27;annoncer la sortie de notre extension Elastic pour l&#x27;interface de ligne de commande Gemini de Google, qui apporte toute la puissance de <a href="https://www.elastic.co/elasticsearch">Elasticsearch</a> et <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a> directement dans votre workflow de développement d&#x27;IA. Cette extension propose également plusieurs compétences d’agent récemment développées pour interagir avec Elasticsearch.</p><p>L&#x27;extension est disponible en tant que projet open source <a href="https://github.com/elastic/gemini-cli-elasticsearch">ici</a>.</p><h2>Qu&#x27;est-ce que Gemini CLI et comment l&#x27;installer ?</h2><p><a href="https://geminicli.com/">Gemini CLI</a> est un agent d’IA open source qui intègre les modèles Gemini de Google directement dans la ligne de commande. Il permet aux développeurs d’interagir avec l’IA depuis le terminal pour effectuer des tâches telles que générer du code, éditer des fichiers, exécuter des commandes shell et récupérer des informations sur le web.</p><p>Contrairement aux interfaces de chat classiques, Gemini CLI s&#x27;intègre à votre environnement de développement local, ce qui signifie qu&#x27;il peut comprendre le contexte du projet, modifier des fichiers, assurer l&#x27;exécution des compilations ou des tests et automatiser les workflows directement dans le terminal. Il est donc utile aux développeurs, aux ingénieurs de fiabilité des sites (SRE) et aux ingénieurs qui souhaitent un codage assisté par l&#x27;IA et une automatisation sans quitter leur workflow en ligne de commande.</p><p>Le CLI Gemini s’installe à l’aide de plusieurs gestionnaires de paquets. La méthode la plus courante passe par npm :</p><pre><code>npm install -g @google/gemini-cli</code></pre><p>Si vous souhaitez connaître d’autres options d’installation, consultez la <a href="https://geminicli.com/docs/get-started/installation/">page officielle d’installation</a>.</p><p>Après l’installation, lancez la CLI en exécutant :</p><pre><code>gemini</code></pre><p>Vous voyez un écran, comme illustré sur la figure 1 :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6dfcec4a77b3dc83bf0d974417bf2e211abb1f4f-876x468.png" style="width: 100%;" /><h2>Configurer Elasticsearch</h2><p>Nous avons besoin d&#x27;une instance Elasticsearch en cours d&#x27;exécution. Si vous souhaitez utiliser le serveur Model Context Protocol (MCP), vous devez également installer Kibana 9.3+. Pour utiliser le langage de requête Elasticsearch (ES|QL) (<code>esql</code>) décrit ci-dessous, Kibana n’est pas requise.</p><p>Vous pouvez activer un essai gratuit sur <a href="https://www.elastic.co/cloud">Elastic Cloud</a> ou l’installer localement en utilisant le script <a href="https://github.com/elastic/start-local"><code>start-local</code></a> :</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>Cela installera Elasticsearch et Kibana sur votre ordinateur et générera une clé API à utiliser pour configurer Gemini CLI.</p><p>La clé API sera affichée comme sortie de la commande précédente et stockée dans un fichier <strong>.env</strong> fichier dans le dossier <strong><code>elastic-start-local</code></strong>.</p><p>Si vous utilisez Elasticsearch sur site (par exemple, en utilisant <code>start-local</code>), et que vous souhaitez utiliser Elastic Agent Builder avec MCP, vous devez aussi connecter un grand modèle de langage (LLM). Vous pouvez consulter <a href="https://www.elastic.co/docs/explore-analyze/ai-features/llm-guides/llm-connectors">cette page de documentation</a> pour comprendre les différentes options.</p><p>Si vous utilisez Elastic Cloud (ou Elastic Cloud Serverless), vous disposez déjà d’une connexion LLM préconfigurée.</p><h2>Installez l&#x27;extension Elasticsearch</h2><p>Vous pouvez installer l&#x27;extension Elasticsearch pour Gemini CLI avec la commande suivante :</p><pre><code>gemini extensions install https://github.com/elastic/gemini-cli-elasticsearch</code></pre><p>Vous pouvez vérifier que les extensions ont été installées avec succès en ouvrant Gemini et en exécutant la commande suivante :</p><pre><code>/extensions list</code></pre><p>L&#x27;extension Elasticsearch devrait être disponible.</p><p>Si vous souhaitez utiliser l&#x27;intégration MCP, vous devez avoir une version d&#x27;Elasticsearch 9.3+ installée. Vous avez besoin de l’URL de votre serveur MCP depuis <a href="https://www.elastic.co/kibana">Kibana</a> :</p><ul><li>Obtenez l&#x27;URL de votre serveur MCP dans Agents &gt; Voir tous les outils &gt; Gérer MCP &gt; Copier l&#x27;URL du serveur MCP.</li><li>L&#x27;URL se présentera comme suit : https://your-kibana-instance/api/agent_builder/mcp</li></ul><p>Vous avez besoin de l’URL de l&#x27;endpoint Elasticsearch. Ce message apparaît généralement en haut de la page Elasticsearch de Kibana. Si vous utilisez Elasticsearch avec <code>start-local</code>, vous avez déjà l&#x27;endpoint dans la clé <code>ES_LOCAL_URL</code> dans le fichier<code>start-local</code> .env.</p><p>Vous avez également besoin d’une clé API. Si vous exécutez Elasticsearch avec <code>start-local</code>, vous avez déjà le <code>ES_LOCAL_API_KEY</code> dans le fichier <code>start-local</code> .env. Sinon, vous pouvez créer une clé API en utilisant l’interface Kibana, comme indiqué <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">ici</a> :</p><ul><li>Dans Kibana : Stack Management &gt; Security &gt; Clés API &gt; Créer une clé API.</li><li>Nous suggérons de définir uniquement les privilèges de lecture pour la clé API, en activant le privilège <code>feature_agentBuilder.read</code> comme indiqué <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/permissions#grant-access-with-roles">ici</a>.</li><li>Copiez la valeur de la clé API encodée.</li></ul><p>Définissez les variables d&#x27;environnement requises dans votre shell :</p><pre><code>export ELASTIC_URL="your-elasticsearch-url"
export ELASTIC_MCP_URL="your-elasticsearch-mcp-url"
export ELASTIC_API_KEY="your-encoded-api-key"</code></pre><h2>Installer l&#x27;ensemble de données d&#x27;exemple</h2><p>Vous pouvez installer l&#x27;ensemble de données <strong>eCommerce orders </strong>disponible dans Kibana. Il comprend un seul index nommé <strong><code>kibana_sample_data_ecommerce</code></strong>, contenant des informations sur 4 675 commandes provenant d&#x27;un site web. Pour chaque commande, nous disposons des informations suivantes :</p><ul><li>Informations client (nom, identifiant, date de naissance, e-mail, etc.).</li><li>Date de la commande.</li><li>ID de commande.</li><li>Produits (liste de tous les produits avec prix, quantité, identification, catégorie, réduction et autres détails).</li><li>SKU.</li><li>Prix total (hors taxes, taxes incluses).</li><li>Quantité totale.</li><li>Informations géographiques (ville, pays, continent, localisation, région).</li></ul><p>Pour installer les données d&#x27;exemple, ouvrez la page <strong>Intégrations</strong> dans Kibana (recherchez « Intégrations » dans la barre de recherche supérieure) et installez l&#x27;<strong>ensemble de données</strong> « Échantillons de données ». Pour plus de détails, consultez la documentation <a href="https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana">ici</a>.</p><p>Le but de cet article est de montrer à quel point il est facile de configurer la CLI Gemini pour se connecter à Elasticsearch et interagir avec l&#x27;index <strong><code>kibana_sample_data_ecommerce</code></strong>.</p><h2>Comment utiliser le MCP d’Elasticsearch</h2><p>Vous pouvez vérifier la connexion à l&#x27;aide de la commande suivante dans Gemini :</p><pre><code>/mcp list</code></pre><p>Le <strong><code>elastic-agent-builder</code></strong> devrait être activé, comme le montre la figure 2 :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1508423bc1d1bc3c04a1cb01e2d59495a3516ed1-1465x844.png" style="width: 100%;" /><p>Elasticsearch fournit un ensemble d&#x27;outils par défaut. Voir la description <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/tools/builtin-tools-reference">ici</a>.</p><p>Grâce à ces outils, vous pouvez interagir avec Elasticsearch, en posant des questions telles que :</p><ul><li><code>Give me the list of all the indexes available in Elasticsearch.</code></li><li><code>How many customers are based in the USA in the kibana_sample_data_ecommerce index of Elasticsearch?</code></li></ul><p>En fonction de la question, Gemini utilisera un ou plusieurs des outils disponibles pour tenter d&#x27;y répondre.</p><h2>Les commandes /elastic</h2><p>Dans l’extension Elasticsearch pour Gemini CLI, nous avons également ajouté<strong> <code>/elastic</code></strong> commandes.</p><p>Si vous exécutez la commande <strong><code>/help</code></strong>, vous verrez toutes les options <code>/elastic</code> disponibles (Figure 3) :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5b2a0727ce7a04354878dd048253d3f4d062324b-1983x230.png" style="width: 100%;" /><p>Ces commandes peuvent être utiles si vous souhaitez exécuter directement un outil spécifique du serveur MCP <code>elastic-agent-builder</code>. Par exemple, en utilisant la commande suivante, vous pouvez obtenir le mapping de <code>kibana_sample_data_ecommerce</code> :</p><pre><code>/elastic:get-mapping kibana_sample_data_ecommerce</code></pre><p>Ces commandes sont essentiellement des raccourcis permettant d’exécuter des outils spécifiques, plutôt que de s’en remettre au modèle Gemini pour déterminer l’outil à invoquer.</p><h2>Comment utiliser les compétences Elasticsearch</h2><p>Cette extension inclut également une <a href="https://github.com/elastic/gemini-cli-elasticsearch/tree/main/skills/esql">compétence d’agent pour ES|QL</a>, le <a href="https://www.elastic.co/docs/explore-analyze/discover/try-esql">langage de requête canalisé d’Elasticsearch (ES|QL)</a> disponible dans Elasticsearch. <a href="https://agentskills.io/home">Agent Skills</a> est un format ouvert qui fournit aux agents IA de codage, comme Gemini CLI, des instructions personnalisées pour des tâches spécifiques. Ils utilisent un concept appelé <em>divulgation progressive</em>, ce qui signifie que seule une brève description de la compétence est ajoutée au prompt système initial. Lorsque vous demandez à l’agent d’effectuer une tâche, comme interroger Elasticsearch, il fait correspondre la requête à la compétence pertinente et charge dynamiquement les instructions détaillées. Il s’agit d’un moyen efficace de gérer les budgets de tokens tout en fournissant à l’IA exactement le contexte dont elle a besoin.</p><p>La<strong> compétence</strong> <strong><code>esql</code></strong>est conçue pour permettre à Gemini CLI d’écrire et d’exécuter des requêtes ES|QL directement sur votre cluster. ES|QL est un langage de requête puissant qui rend l&#x27;exploration des données, l&#x27;analyse des logs et les agrégations très intuitives. Avec cette compétence activée, vous n&#x27;avez pas besoin de rechercher la syntaxe ES|QL ; vous pouvez simplement poser des questions en langage naturel à l&#x27;interface en ligne de commande Gemini sur vos données, et l&#x27;agent se chargera du reste.</p><p>Les exécutions sont réalisées à l&#x27;aide de simples commandes <a href="https://curl.se/">curl</a> lancées dans un terminal. L’intégration d’Elasticsearch à n’importe quelle architecture est simplifiée par la richesse de ses API REST.</p><p><strong>Ce que la </strong>compétence<strong><code>esql</code> offre :</strong></p><ul><li><strong>Recherche d&#x27;index et de schémas :</strong> L&#x27;agent peut utiliser les outils intégrés de la compétence pour dresser la liste des index disponibles et récupérer le mapping des champs. Par exemple, avant d’écrire une requête pour l’ensemble de données eCommerce, l’agent peut effectuer une exécution de vérification de schéma sur <strong><code>kibana_sample_data_ecommerce</code></strong> afin de comprendre les champs disponibles, comme <strong><code>taxful_total_price</code></strong> ou <strong><code>category</code></strong>.</li><li><strong>Traduction transparente en langage naturel :</strong> La compétence donne à l&#x27;agent plus qu&#x27;un simple manuel de référence ; elle lui fournit un guide spécifique pour interpréter l&#x27;intention de l&#x27;utilisateur. Dès que vous tapez une demande en langage naturel, par exemple « Afficher le temps de réponse moyen groupé par service », l’agent s’appuie sur les modèles intégrés de la compétence pour convertir vos mots en commandes, filtres et agrégations ES|QL appropriés.</li><li><strong>Autocorrection :</strong> en cas d’échec d’une requête (erreur de syntaxe ou de type, par exemple), la compétence transmet la requête ainsi que l’erreur Elasticsearch précise. L’agent peut alors la rectifier immédiatement et retenter l’opération sans que vous ayez à intervenir.</li></ul><p>Comme la compétence <code>esql</code> est également disponible sous forme d&#x27;outil sur le serveur MCP <code>elastic-agent-builder</code>, nous devons désactiver ce serveur temporairement. Vous pouvez utiliser la commande suivante pour le désactiver :</p><pre><code>/mcp disable elastic-agent-builder</code></pre><p>Ensuite, vous pouvez simplement taper une invite comme celle-ci dans votre interface de ligne de commande Gemini :</p><pre><code>Find the top 5 product categories by total sales revenue in the kibana_sample_data_ecommerce index</code></pre><p>L’agent devra :</p><ul><li>Reconnaissez la nécessité de la compétence <code>esql</code>.</li><li>Consultez le schéma de <strong><code>kibana_sample_data_ecommerce</code></strong>.</li><li>Construisez une requête ES|QL, comme : <code>FROM kibana_sample_data_ecommerce | STATS total_revenue = SUM(taxful_total_price) BY category.keyword | SORT total_revenue DESC | LIMIT 5</code>.</li><li>Exécutez la requête auprès de l’API Elasticsearch.</li><li>Présentez la réponse finale directement dans le terminal.</li></ul><p>Nous avons cité ici un exemple de réponse de Gemini à la question précédente :</p><pre><code>───────────────────────────────────────────────────────────
> Find the top 5 product categories by total sales revenue in the kibana_sample_data_ecommerce index   ───────────────────────────────────────────────────────────
✓  Activate Skill "esql": Interact with Elasticsearch using ES|QL and curl. Use when querying, managing indices,  …
Skill esql activated. Resources loaded from /home/user/.gemini/extensions/elasticsearch/skills/esql: 
Showing up to 200 items (files + folders).                                                                     /home/user/.gemini/extensions/elasticsearch/skills/esql/                                                            ├───SKILL.md 
└───references/
├───esql-reference.md 
├───generation-tips.md 
└───query-patterns.md

✦ I will start by exploring the mapping of the kibana_sample_data_ecommerce index to identify the fields for product category and sales revenue. Then, I will construct and execute an ES|QL query to determine the top 5 product categories by total sales revenue.

✓  Shell curl -s "${ELASTIC_URL%/}/kibana_sample_data_ecommerce/_mapping" -H "Authorization: ApiKey $(printenv ELAS…                                                                                                                       {  
   "kibana_sample_data_ecommerce": { 
     "mappings": { 
       "properties": {
         "category": { 
           "type": "text", 
           "fields": {
             "keyword": {
               "type": "keyword"
             }
           }
         }, 
         "currency": {
           "type": "keyword"
         }, 

✦ Okay, I have the mapping. The category is products.category.keyword and sales revenue is taxful_total_price. I'll now execute the ES|QL query to get the top 5 categories by revenue.

✓  Shell curl -s -X POST "${ELASTIC_URL%/}/_query" \  -H "Authorization: ApiKey $(printenv ELASTIC_API_KEY)" \  -H …
{ 
   "took": 62,
   "is_partial": false, 
   "completion_time_in_millis": 1772805644164,
   "documents_found": 4675,
   "values_loaded": 12084, 
   "start_time_in_millis": 1772805644102, 
   "expiration_time_in_millis": 1773237644087,  
   "columns": [ 
     { 
       "name": "total_revenue",    
       "type": "double"  
     }, 
     {   
       "name": "products.category.keyword",

✦ The query for top 5 product categories by revenue was successful. I'll format the returned JSON values into a   user-friendly list.

 ✓  Shell 
                             
 Top 5 product categories by total sales revenue: 
 1. Men's Clothing: 149393.91
 2. Women's Clothing: 135099.91
 3. Women's Shoes: 105479.17
 4. Men's Shoes: 91797.92
 5. Women's Accessories: 60830.31 

✦ I've found the answer; now I'll format it for clarity.

 ✓  Shell 

 Here are the top 5 product categories by total sales revenue:                                                        
                                                                      
 | Rank | Category              | Total Revenue | 
 |------|-----------------------|---------------| 
 | 1    | Men's Clothing        | 149,393.91    |
 | 2    | Women's Clothing      | 135,099.91    |
 | 3    | Women's Shoes         | 105,479.17    |
 | 4    | Men's Shoes           | 91,797.92     | 
 | 5    | Women's Accessories   | 60,830.31     | 

✦ I've formatted and presented the answer. I'm finished.
 ───────────────────────────────────────────────────────────</code></pre><p>Il est intéressant de noter comment le modèle Gemini génère la réponse finale en montrant toutes les étapes qu’il suit. On peut voir ici l’influence de la compétence sur la démarche de réflexion du modèle. Dès que le modèle identifie la nécessité d’utiliser une compétence ou d’exécuter une commande shell, il sollicite une autorisation via l’approche supervision humaine dans la boucle.</p><p>Grâce à la gestion automatisée de la découverte de schéma, de la génération de requêtes et de leur exécution, la compétence <code>esql</code> vous libère des contraintes techniques pour vous focaliser uniquement sur l’analyse des résultats. Vous obtiendrez les données dont vous avez besoin, correctement formatées et directement dans votre terminal, sans jamais écrire une seule ligne de code ni basculer vers une autre application.</p><h2>Conclusion</h2><p>Dans cet article, nous avons présenté l&#x27;extension Elasticsearch pour Gemini CLI que nous avons récemment publiée. Cette extension vous permet d&#x27;interagir avec votre instance Elasticsearch en utilisant Gemini et le serveur Elasticsearch MCP fourni par Elastic Agent Builder, disponible à partir de la version 9.3.0, ainsi que la commande <code>/elastic</code>.</p><p>De plus, l&#x27;extension comprend également une compétence <code>esql</code> qui convertit la demande d&#x27;un utilisateur en langage naturel en une requête ES|QL. Cette compétence est très pratique quand l’usage du serveur MCP est impossible, puisque les échanges s’appuient sur l’exécution de commandes curl basiques dans le terminal. L’intégration d’Elasticsearch à tous vos projets est simplifiée par la richesse de ses API REST. C’est particulièrement utile lors du développement d’applications d’IA agentique.</p><p>Pour plus d’informations sur notre extension Gemini CLI, visitez le dépôt de projets <a href="https://github.com/elastic/gemini-cli-elasticsearch">ici</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Compétences d'agent pour Elastic : transformez votre agent IA en un expert Elastic]]></title>
            <description><![CDATA[Donnez à votre agent de codage IA les connaissances nécessaires pour interroger, visualiser, sécuriser et automatiser avec les compétences Elastic Agent.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/agent-skills-elastic</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/agent-skills-elastic</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Graham Hudgins, Matt Ryan]]></dc:creator>
            <pubDate>Mon, 16 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/09e64953819083168a9ecef0888c7f8bde1a43bd-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Chaque développeur, ingénieur SRE ou analyste qui a essayé d&#x27;utiliser un agent IA de programmation avec une plateforme spécialisée s&#x27;est heurté au même obstacle. Vous demandez à l&#x27;agent de rédiger une requête, de configurer une alerte ou d&#x27;enquêter sur un point, et il s&#x27;en sort presque, mais pas tout à fait. Elastic a un avantage ici : le fait qu&#x27;il existe plus d&#x27;une décennie de documentation, d&#x27;articles de blog et de réponses de la communauté fait que les agents IA connaissent déjà Elastic mieux que la plupart des plateformes de données. Mais cette richesse s&#x27;accompagne d&#x27;un certain désordre. Les API obsolètes coexistent avec les API actuelles. Les modèles obsolètes sont classés au même niveau que les bonnes pratiques. L&#x27;agent reproduit avec confiance une approche qui fonctionnait il y a trois versions, car dans ses données d&#x27;entraînement, c&#x27;était le cas. Il en résulte un fardeau de correction : les utilisateurs alimentent manuellement la documentation dans le contexte, corrigent la syntaxe hallucinée et contournent l&#x27;agent au lieu de travailler avec lui. Pire encore, les fonctionnalités avancées restent totalement inutilisées, non pas parce que les utilisateurs n’en ont pas besoin, mais parce que l’agent ignore leur existence.</p><p>C&#x27;est pourquoi nous mettons en open source les <a href="https://github.com/elastic/agent-skills">compétences Elastic Agent</a> : une expertise native de la plateforme pour Elasticsearch, Kibana, Elastic Observability et Elastic Security. Ajoutez-les dans l&#x27;environnement d&#x27;exécution de l&#x27;agent que vous utilisez déjà, et améliorez votre agent en le faisant passer d&#x27;un statut de &quot;généraliste&quot; qui devine un grand nombre de syntaxes à un statut d&#x27;expert, capable par exemple d&#x27;utiliser un grand nombre de normes architecturales comme le font les équipes d&#x27;ingénieurs d&#x27;Elastic. Cette première version technique se concentre sur les compétences avec une compatibilité maximale pour <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a>, mais évoluera rapidement pour inclure une meilleure prise en charge des anciennes versions de la pile.</p><p>De plus, Elastic résout ce problème sur les deux fronts. Pour les agents sur la plateforme Elastic, <a href="https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga">Elastic Agent Builder</a> (désormais disponible en version générale) vous permet de créer et de discuter avec des agents IA qui héritent des contrôles d&#x27;accès de vos données, utilisent des outils de recherche et d&#x27;analyse intégrés, et travaillent en contexte aux côtés de vos tableaux de bord, alertes et investigations. Nous travaillons dur pour garantir des expériences exceptionnelles d’agent sur la plateforme Elastic. Mais tous les agents ne se trouvent pas au sein d’Elastic. Votre équipe utilise déjà Cursor, Claude Code ou d&#x27;autres environnements d&#x27;exécution, et ces agents doivent également maîtriser Elastic. C&#x27;est là que les compétences des agents entrent en jeu.</p><h2>Pourquoi les agents rencontrent-ils des difficultés avec les plateformes spécialisées</h2><p>Les grands modèles de langage (LLM) sont des généralistes remarquablement compétents. Ils peuvent écrire en Python, expliquer les manifestes Kubernetes et restructurer les composants React car leurs données d&#x27;entraînement sont riches en exemples. Mais lorsqu’il s’agit de travaux spécifiques à la plateforme, ceux qui impliquent des langages de requête propriétaires, des interfaces API complexes et des bonnes pratiques spécifiques à un domaine, ils échouent de manière prévisible.</p><p>Pour Elasticsearch, l&#x27;écart se manifeste concrètement :</p><ul><li><strong>Le langage de requête Elasticsearch (ES|QL) est un nouveau domaine.</strong> Les LLM sont fortement entraînés au SQL, mais ES|QL est un langage de requête canalisé avec une syntaxe différente, des fonctions différentes et une sémantique différente. Les agents écrivent fréquemment des requêtes qui semblent plausibles mais ne s&#x27;analysent pas. Ils confondent <code>WHERE</code> et <code>| WHERE</code>, inventent des fonctions qui n&#x27;existent pas et passent complètement à côté du modèle de composition canalisé.</li><li>Les <strong>surfaces API sont larges et profondes.</strong> Elasticsearch, Kibana et Elastic Security exposent des centaines d&#x27;API dans les domaines de la recherche, de l&#x27;ingestion, de l&#x27;alerting, des règles de détection, de la gestion des cas, des tableaux de bord et plus encore. Un agent ne disposant que de données d&#x27;entraînement générales doit deviner quel point de terminaison appeler, à quoi ressemble le corps de la requête et comment gérer la réponse. Il se trompe suffisamment souvent pour éroder la confiance.</li><li><strong>Les bonnes pratiques ne figurent pas dans les données d&#x27;entraînement.</strong> Quand devez-vous utiliser <code>semantic_text</code> plutôt qu&#x27;un pipeline de plongement personnalisé ? Comment structurer un pipeline d&#x27;ingestion pour un CSV de 10 Go ? Quelle est la bonne syntaxe de règle de détection pour une technique <a href="https://www.elastic.co/docs/solutions/security/detect-and-alert/mitre-attandckr-coverage">MITRE ATT&amp;CK</a> ? Les agents polyvalents ne disposent pas de connaissances spécifiques à Elastic organisées et structurées de manière fiable et chargées par défaut. Ils devraient aller les chercher, et même s&#x27;ils le faisaient, les documents bruts ne reflètent pas toujours les jugements et les bonnes pratiques que les praticiens qualifiés appliquent.</li></ul><p>Résultat : les développeurs passent plus de temps à corriger les sorties des agents qu&#x27;à écrire le code eux-mêmes. Ce n&#x27;est pas l&#x27;expérience pour laquelle ils se sont engagés.</p><h2>Compétences des agents : connaissances de la plateforme, destinées aux agents</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8d16ec00d16e70a916c5eef0aaa23fcc735b7186-1067x1280.png" style="width: 100%;" /><p>Les compétences des agents sont des répertoires autonomes d&#x27;instructions, de scripts et de matériel de référence que les environnements d&#x27;exécution des agents peuvent charger de manière dynamique. Lorsqu’une compétence est active, l’agent a accès au bon contexte au bon moment : syntaxe de requête, modèles d’API, logique de validation, exemples pratiques, afin de pouvoir exécuter correctement les tâches du premier coup.</p><p>Chaque compétence suit la spécification ouverte <a href="https://agentskills.io">agentskills.io</a> : un dossier avec un fichier <code>SKILL.md</code> contenant des métadonnées et des instructions structurées. Aucun format propriétaire, pas de dépendance. Les compétences fonctionnent à travers les environnements d&#x27;exécution des agents, notamment Cursor, Claude Code, GitHub Copilot, Windsurf, Gemini CLI, Cline, Codex, et <a href="https://agentskills.io">bien d’autres</a>.</p><h3>Contenu de la version initiale v0.1.0</h3><p>Le premier ensemble de compétences couvre cinq domaines de l’Elastic Stack :</p><ul><li>Interagir avec les API Elasticsearch (recherche, indexation, clustering)</li><li>Création et gestion de contenu Kibana tels que les tableaux de bord, les alertes, les connecteurs et plus encore.</li><li>Expertise de domaine pour Elastic Observability</li><li>Expertise de domaine pour Elastic Security</li><li>Créer des agents efficaces dans Agent Builder</li></ul><h3>Les compétences sont composables</h3><p>Les compétences ne sont pas monolithiques. Elles sont modulaires de par leur conception. Votre agent charge uniquement les compétences pertinentes pour la tâche en cours. Vous travaillez sur une requête ES|QL ? La compétence ES|QL est activée. Vous souhaitez créer un tableau de bord à partir de ces résultats ? La compétence tableaux de bord se lance. Évaluer la santé de votre application ? La compétence de santé des services entre en jeu. Enquêter sur une alerte de sécurité ? Les compétences de triage s&#x27;enchaînent avec celles de gestion de cas et de réponse au fur et à mesure que l&#x27;enquête progresse.</p><p>Cette composabilité signifie que vous n&#x27;avez pas besoin d&#x27;une invite unique et massive qui tente de tout couvrir. Chaque compétence comporte exactement le contexte requis par son domaine, ni plus ni moins.</p><h2>Pour les développeurs d&#x27;applications de recherche et d&#x27;IA</h2><p>Si vous chargez des données dans Elasticsearch, que vous rédigez des requêtes ou que vous migrez des index, les compétences réduisent le cycle de génération de code, de détection d&#x27;erreurs et de recherche dans la documentation pour trouver ce qui n&#x27;a pas fonctionné.</p><p>Demandez à votre agent de charger un fichier CSV ; celui-ci utilisera un outil d’ingestion en continu qui gérera la contre-pression et déduira les mapping à partir des données. Il ne s&#x27;agit pas d&#x27;une boucle _bulk exécutée à la main qui épuise la mémoire dès le premier fichier volumineux. Demandez-lui de faire une requête auprès d’ES|QL, et il découvre vos véritables noms d’index et schémas de champs, puis écrit des requêtes valides canalisées avec la bonne syntaxe, les agrégations appropriées et la sélection de fonctionnalités adaptée à la version, et non une supposition SQL nécessitant trois tours de débogage. Demandez-lui de réindexer sur plusieurs clusters, et il suit le workflow complet : il crée la destination avec des mappings explicites, ajuste les paramètres de débit, effectue l&#x27;exécution de la tâche de manière asynchrone et restaure les paramètres de production une fois celle-ci terminée, et non pas un simple appel _reindex qui saute la moitié des étapes qu’un opérateur expérimenté suivrait.</p><p>Au lieu d&#x27;un agent qui vous donne un point de départ plausible à corriger, vous en obtenez un qui encode la discipline opérationnelle qui permet à la sortie de fonctionner réellement.</p><p><strong>Exemples d&#x27;impacts de l&#x27;utilisation des compétences d&#x27;Elastic Agent</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Eval</td><td style="border: 1px solid #ccc; padding: 4px;">Ce que la compétence a changé</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-requêtes-d'audit-échecs-de-connexion</td><td style="border: 1px solid #ccc; padding: 4px;">Utilisation des modèles de requête du log d'audit à partir de la compétence au lieu d'une recherche générique</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-autorisation-mapping-de-rôle-ldap</td><td style="border: 1px solid #ccc; padding: 4px;">Émission de la structure correcte de l'appel d'API de mapping des rôles</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">esql-requête-de-base</td><td style="border: 1px solid #ccc; padding: 4px;">Écriture de la syntaxe canalisée d'ES|QL via Query DSL</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">esql-gestion-des-erreurs</td><td style="border: 1px solid #ccc; padding: 4px;">Priorité au schéma au lieu de deviner les noms des champs</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">esql-découverte de schéma</td><td style="border: 1px solid #ccc; padding: 4px;">Ne devinez jamais le nom d'un index</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-ingestion-csv-avec-inférence</td><td style="border: 1px solid #ccc; padding: 4px;">Utilisation de --infer-mapping uniquement, évitant de le combiner avec --source-format CSV qui crée un index vide</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-ingestion-fichier-json</td><td style="border: 1px solid #ccc; padding: 4px;">Utilisation d'une approche d'ingestion robuste capable de traiter des fichiers volumineux</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-réindexer-local-asynchrone</td><td style="border: 1px solid #ccc; padding: 4px;">Création de l’index de destination avec les répliques : 0 et refresh_interval : « -1 », puis réindexation asynchrone. Base : pas de préparation</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-sécurité-403-privilèges</td><td style="border: 1px solid #ccc; padding: 4px;">Suivi d'un workflow de diagnostic de la compétence pour les erreurs de privilège au lieu de conseils génériques.</td>
                    </tr>
              </tbody>
            </table>
          <h2>Pour les équipes de sécurité</h2><p>Security répète quotidiennement les mêmes workflows opérationnels : trier les alertes, ajuster les règles de détection, gérer les dossiers. Les compétences de l&#x27;agent encodent ces connaissances procédurales afin que votre agent IA puisse exécuter correctement ces workflows, en appelant les bonnes API dans le bon ordre et avec les bons noms de champ. Pour une présentation pratique qui vous permettra de passer de zéro à un environnement Elastic Security complet sans quitter votre IDE, consultez la section <a href="https://www.elastic.co/security-labs/agent-skills-elastic-security">Prise en main d&#x27;Elastic Security depuis votre agent d&#x27;IA</a>.</p><h2>Pour les équipes d&#x27;observabilité et d&#x27;opérations</h2><p>Les nouvelles compétences des agents pour Elastic Observability réduisent les tâches opérationnelles liées à l&#x27;instrumentation de systèmes complexes, à la gestion des SLO, au tri des données complexes et à l&#x27;évaluation de l&#x27;état des services. L&#x27;intégration de l&#x27;expertise native d&#x27;Elastic directement dans les agents IA permet aux équipes d&#x27;exécuter des workflows d&#x27;observabilité complexes en utilisant un langage naturel simple. Cela permet aux équipes SRE et chargées des opérations de résoudre les incidents plus rapidement et d&#x27;assurer la maintenance de systèmes fiables plus facilement. Pour en savoir plus, consultez <a href="https://www.elastic.co/observability-labs/blog/elastic-agent-skills-observability-workflows">cet article de blog</a>.</p><h2>Open source, spécifications ouvertes, piloté par la communauté</h2><p>Nous publions les compétences d&#x27;agent sous la licence Apache 2.0, car nous pensons que les connaissances des agents doivent être ouvertes. La spécification <a href="https://agentskills.io">agentskills.io</a> que suivent les compétences est un standard ouvert, et non un format propriétaire d&#x27;Elastic. Nous voulons que les compétences soient le fruit d&#x27;un effort communautaire, et non d&#x27;un lieu clos.</p><h2>Une partie d&#x27;un tableau plus vaste</h2><p>Agent Skills fait partie d&#x27;une initiative plus vaste visant à faire d&#x27;Elasticsearch la plateforme de données la plus adaptée aux agents disponible sur le marché. Pour les agents hébergés sur la plateforme Elasticsearch, <a href="https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga">Agent Builder</a> va plus loin en reprenant les contrôles d&#x27;accès et les autorisations de vos données, en fournissant des outils intégrés et personnalisés pour la recherche et l&#x27;analyse, et en permettant aux utilisateurs d&#x27;interagir avec les agents en contexte, au sein de leurs tableaux de bord, alertes et enquêtes. Enfin, la prise en charge des compétences sera bientôt disponible dans Agent Builder, offrant aux développeurs la flexibilité nécessaire pour tirer parti des compétences Elastic Agent ainsi que de celles provenant de toute autre source, afin de permettre un chat sécurisé et enrichi par le contexte, ainsi qu&#x27;une automatisation sur la plateforme Elasticsearch.</p><p>Pour les agents qui vivent ailleurs, nous investissons dans l&#x27;écosystème ouvert :</p><ul><li><strong>Extension du serveur Model Context Protocol (MCP) :</strong> Extension du <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">point de terminaison MCP</a> dans Agent Builder avec davantage d&#x27;outils au-delà des opérations actuelles de recherche, ES|QL et d&#x27;index.</li><li><strong>Améliorations de l&#x27;authentification :</strong> faciliter la connexion sécurisée des agents, dans le but d&#x27;éliminer le copier-coller manuel des clés API.</li><li><strong>Documentation lisible par LLM :</strong> publication des fichiers <code>llms.txt</code> et <code>AGENTS.md</code> afin que les agents puissent découvrir et comprendre les API Elastic par eux-mêmes.</li><li><strong>Une interface de ligne de commande (CLI) pour les flux de travail des agents :</strong> Un outil de ligne de commande qui facilite la gestion des connexions et les opérations courantes pour les agents.</li></ul><p>Les compétences sont la partie que vous pouvez utiliser aujourd&#x27;hui. Le reste est à venir.</p><h2>Lancez-vous</h2><p><strong>Avant de commencer : </strong>Les agents de codage d’IA fonctionnent avec de vraies informations d’identification, un véritable accès au shell et, souvent, avec toutes les autorisations de l’utilisateur qui les exécute. Lorsque ces agents sont orientés vers des workflows de sécurité, les enjeux sont plus élevés : vous confiez à un système automatisé l’accès à la logique de détection, aux actions de réponse et aux télémétries sensibles. Le profil de risque de chaque organisation est différent. Avant d’activer les workflow de sécurité pilotés par l’IA, <strong>évaluez les données auxquelles l’agent peut accéder, les actions qu’il peut entreprendre et ce qui se passe s’il se comporte de manière inattendue</strong>.</p><p>Installez les compétences Elastic Agent dans votre environnement d&#x27;exécution de l&#x27;agent :</p><p><code>npx skills add elastic/agent-skills</code></p><p>Cela détecte automatiquement vos agents d&#x27;exécution installés et place les compétences dans le répertoire de configuration approprié. A partir de là, votre agent les récupère automatiquement.</p><p>Vous pouvez également consulter directement le <a href="https://github.com/elastic/agent-skills">catalogue de compétences</a> et installer manuellement des compétences individuelles en copiant le dossier de compétences dans le répertoire de configuration de votre agent.</p><p>Vous n&#x27;avez pas encore de cluster Elasticsearch ? Démarrer un <a href="https://cloud.elastic.co/registration">essai gratuit d&#x27;Elastic Cloud</a>. Il faut environ une minute pour obtenir un environnement entièrement configuré.</p><p><strong>Explorez le projet :</strong></p><ul><li><a href="https://github.com/elastic/agent-skills">Répertoire des compétences des agents</a></li><li><a href="https://agentskills.io">Spécifications d&#x27;agentskills.io</a></li><li><a href="https://www.elastic.co/docs">Documentation Elasticsearch</a></li><li><a href="https://cloud.elastic.co/registration">Essai gratuit d&#x27;Elastic Cloud</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Résolution d'entités avec Elasticsearch, partie 4 : le défi ultime]]></title>
            <description><![CDATA[Relever et évaluer les problématiques de réconciliation d’entités dans un ensemble de données complexe et varié, dont la structure interdit l’usage de méthodes simplifiées ou de contournements.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/entity-resolution-elasticsearch-llm-challenges</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/entity-resolution-elasticsearch-llm-challenges</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Nous avons maintenant vu la résolution intelligente des entités implémentée de deux manières. Les deux approches commencent de la même manière : préparation et extraction des entités, suivies de la récupération des candidats avec Elasticsearch. À partir de là, nous évaluons ces candidats en utilisant un grand modèle de langage (LLM), soit par génération JSON basée sur des invites, soit par appel de fonction, et nous demandons au modèle de fournir une explication transparente de son jugement.</p><p>Comme nous l’avons vu dans l’<a href="https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling">article précédent</a>, cette régularité, permise par l’appel de fonctions, constitue la pierre angulaire de la fiabilité du système, bien au-delà d’un simple gain d’efficacité. Une fois que nous avons éliminé les erreurs structurelles de la boucle d&#x27;évaluation, les résultats sur les scénarios standards (tels que ceux du jeu de données de niveau 4) se sont considérablement améliorés.</p><p>Il reste cependant une interrogation manifeste à laquelle il nous faut répondre :</p><p><em>Cette méthode est-elle toujours viable lorsque les données et les processus s’avèrent véritablement désordonnés ?</em></p><p>En pratique, ce ne sont pas les cas élémentaires qui mettent en défaut les systèmes de réconciliation d’entités. La résolution d’entités s’effondre dès que les noms se heurtent à la diversité des langues, des contextes culturels, des alphabets, des périodes historiques ou des structures administratives différentes. Le système s’effondre quand l’identification repose sur des titres honorifiques, des changements de noms de sociétés ou des translittérations aléatoires, et que seul le contexte environnant permet d’identifier l’entité physique derrière la mention textuelle.</p><p>Donc, pour le dernier billet de cette série, nous avons soumis le système à ce que nous avons appelé <strong>le défi ultime</strong>.</p><h2>Qu’est-ce qui fait de ce test le « défi ultime » ?</h2><p>Nous avons soumis le système à des tests progressifs, en employant des ensembles de données de plus en plus sophistiqués au fil des étapes de validation. Au moment d’atteindre le palier 4, le système gérait déjà des données hybrides mêlant appellations familières, titres honorifiques et variantes linguistiques, exigeant une analyse contextuelle fine. Les tests ont prouvé la pertinence de l’architecture globale, tout en révélant que des erreurs de structure de données, comme des syntaxes JSON incorrectes, bridaient artificiellement les performances de récupération.</p><p>Avec les appels de fonctions en place, nous avions enfin une base stable. Cela nous a donné l&#x27;occasion de poser une question plus intéressante :</p><p><em>Un pipeline unifié peut-il gérer <strong>plusieurs types</strong> de problèmes de résolution d&#x27;entités simultanément ?</em></p><p>Cet ensemble de données de test a été élaboré spécifiquement pour mettre à l’épreuve cette variable critique, en ne laissant aucune place à l’approximation.</p><p>Au lieu de se concentrer sur une seule difficulté (comme les surnoms ou la translittération), cet ensemble de données combine <strong>plus de 50 types de défis distincts</strong>, notamment :</p><ul><li>Conventions de dénomination culturelles.</li><li>Références basées sur les titres.</li><li>Relations commerciales et changements historiques de nom.</li><li>Mentions multilingues et systèmes d’écriture croisés.</li><li>Des défis complexes qui combinent plusieurs des éléments ci-dessus.</li></ul><p>L&#x27;essentiel, ce n&#x27;est pas d&#x27;optimiser pour un cas d&#x27;utilisation restreint. Il s&#x27;agit de vérifier si le <em>modèle de conception</em> tient la route lorsque les règles changent d&#x27;une entité à l&#x27;autre.</p><h2>L&#x27;ensemble de données en un coup d’œil</h2><p>L&#x27;ensemble de données du défi ultime consiste en :</p><ul><li><strong>50 entités</strong>, couvrant des personnes, des organisations et des institutions.</li><li><strong>~60 articles</strong>, dont la structure et la complexité linguistique varient.</li><li><strong>51 catégories de défis distinctes</strong>, regroupées globalement en :<ul><li>Conventions de dénomination culturelles.</li><li>Titres et contexte professionnel.</li><li>Relations commerciales et organisationnelles.</li><li>Les défis du multilinguisme et de la translittération.</li><li>Scénarios combinés et cas limites.</li></ul></li></ul><p>Plus tôt dans cette série, nous avons vu que l’utilisation de l’IA générative (GenAI) pour créer des jeux de données peut s’avérer être une arme à double tranchant. Sans elle, il serait extrêmement difficile de rassembler des données de test suffisamment vastes et diversifiées. Toutefois, sans un contrôle rigoureux, le modèle incline naturellement vers la simplification des cas de test.</p><p>On a remarqué, lors d’un premier passage de génération, que l’IA avait inséré des mentions telles que « le président russe » comme synonymes directs dans la fiche d’identité de Vladimir Poutine. Bien que cela paraisse logique à première vue, une telle approche invalide le test en supprimant la nécessité de comprendre le contexte pour identifier l’entité. Que se passe-t-il si l’article traite de la Russie des années 1990 ? L’objectif est que l’intelligence du moteur réside dans sa capacité d’inférence contextuelle plutôt que dans une simple table de correspondance statique.</p><p>C&#x27;est pourquoi cet ensemble de données a été délibérément conçu pour que les <strong>raccourcis ne fonctionnent pas</strong>. Les alias ne sont pas explicitement énumérés lorsque le système est censé en déduire la signification. Les phrases descriptives ne sont pas pré-liées à des entités. Les bonnes correspondances dépendent souvent du contexte de l&#x27;article, et pas seulement du texte local.</p><p><strong>Remarque importante :</strong> bien que nous démontrions les capacités du système dans divers scénarios, il s&#x27;agit toujours d&#x27;un prototype éducatif. Les systèmes de production gérant la surveillance d&#x27;entités sanctionnées dans le monde réel nécessiteraient une validation supplémentaire, des contrôles de conformité, des pistes d&#x27;audit et une gestion spécialisée pour les cas d&#x27;utilisation sensibles.</p><h2>Pourquoi ces scénarios sont difficiles</h2><p>Dès le premier article de cette série, nous avons introduit un exemple simple mais ambigu : « La nouvelle mise à jour de Swift est arrivée ! » Le défi réside dans le fait que « Swift » peut renvoyer à plusieurs entités du monde réel, selon le contexte. Cet exemple illustre une vérité plus profonde : le langage naturel est intrinsèquement ambigu.</p><p>La résolution d’entités n’est donc pas seulement un problème de correspondance de chaînes de caractères. Nous utilisons instinctivement notre bagage culturel et le contexte immédiat pour interpréter les références, une opération mentale si fluide qu’elle nous semble totalement naturelle.</p><p>Voici quelques cas courants :</p><ul><li>L’expression « le président » est une coquille vide si elle n’est pas ancrée dans une géographie et une époque données.</li><li>Le nom d’une entreprise peut désigner une société mère, une filiale ou une ancienne marque, selon la date à laquelle l’article a été rédigé.</li><li>Le nom d’une personne peut apparaître dans des ordres différents, des alphabets variés ou des translittérations diverses, selon la langue et la culture.</li><li>La même phrase peut légitimement faire référence à des entités différentes dans des contextes différents, et le système doit être en mesure de <em>rejeter</em> les correspondances avec autant d&#x27;assurance qu&#x27;il les accepte.</li></ul><p>Aucun système de règles figées ne peut, à lui seul, traiter l’intégralité de ces nuances de manière satisfaisante. Cette approche explique pourquoi ce prototype applique une séparation des préoccupations aussi stricte :</p><ul><li>Elasticsearch réduit l&#x27;espace réservé aux candidats de manière efficace et transparente.</li><li>Le LLM n’est utilisé que là où un jugement est requis, et il est contraint de justifier sa décision.</li><li>La récupération et le raisonnement demeurent des étapes distinctes.</li></ul><p>Cette distinction devient encore plus importante à mesure que la diversité des types de défis augmente.</p><h2>Comment le système gère la diversité sans recourir à des cas particuliers</h2><p>L&#x27;un des résultats les plus intéressants de cette évaluation est ce qui <em>n’a pas</em> changé :</p><ul><li>Nous <strong>n&#x27;avons pas</strong> ajouté de logique spéciale pour les noms japonais.</li><li>Nous <strong>n&#x27;avons pas</strong> ajouté de règles personnalisées pour les patronymes arabes.</li><li>Nous n&#x27;avons <strong>pas</strong> ajouté de mapping codé en dur pour les noms d&#x27;entreprises historiques.</li></ul><p>À la place, le système s’est appuyé sur les mêmes ingrédients fondamentaux présentés plus tôt dans cette série :</p><ul><li>Entités enrichies par le contexte et indexées pour la recherche sémantique.</li><li>La récupération hybride (exacte, alias et sémantique) dans Elasticsearch.</li><li>Un ensemble restreint et bien défini de candidats.</li><li>Le jugement du LLM est contraint par l’appel de fonctions et des schémas minimaux.</li></ul><p>Cela suggère que la flexibilité du système provient de la <strong>représentation et de l&#x27;architecture</strong>, et non d&#x27;une collection de règles qui ne cesse de croître.</p><p>Lorsque le système réussit, c’est parce que les bons candidats ont été récupérés et que le LLM dispose d’assez de contexte pour expliquer pourquoi une référence correspond (ou non) à une entité spécifique.</p><h2>Résultats : Comment s’est-il comporté ?</h2><p>Sur l’ensemble de données du défi ultime, le système a produit les résultats globaux suivants :</p><ul><li><strong>Précision :</strong> ~91 %</li><li><strong>Rappel :</strong> ~86 %</li><li><strong>Score F1 :</strong> ~89 %</li><li><strong>Taux d&#x27;acceptation des LLM :</strong> ~72 %</li></ul><h3>Performances selon les types de défis</h3><p>L’analyse des résultats par type de défi révèle des forces et des limites bien précises :</p><p><strong>Les performances les plus solides (un score F1 de 100 %)</strong> ont été observées dans des domaines tels que :</p><ul><li>Appariement entre différents systèmes d’écriture (entités commerciales en cyrillique, coréen ou chinois).</li><li>Scénarios en hébreu (patronymes, titres professionnels, titres religieux, translittération).</li><li>Hiérarchies d’entreprises (aérospatiale, industrie diversifiée, conglomérats multidivisionnels).</li><li>Titres professionnels (académiques, militaires, politiques, religieux).</li><li>Scénarios japonais combinés impliquant plusieurs systèmes d&#x27;écriture.</li></ul><p><strong>Une performance solide (score F1 de 80 à 99 %)</strong> a été enregistrée dans les catégories suivantes :</p><ul><li>Personnalités politiques internationales (98 %).</li><li>Changements de noms historiques (90 %).</li><li>Hiérarchies d’entreprise complexes (89 %).</li><li>Noms de sociétés japonais (93 %).</li><li>Translittération entre différents systèmes d’écriture (86 %).</li><li>Patronymes arabes (86 %).</li></ul><p>Les <strong>domaines les plus difficiles</strong> sont les suivants :</p><ul><li>Translittération avancée (chinois, coréen) : 0 % F1.</li><li>Certains scénarios japonais (titres honorifiques, ordre des noms, variations du système d’écriture) : ~67 % F1.</li><li>Quelques scénarios en arabe (noms d&#x27;entreprises, références institutionnelles) : ~40 % F1.</li></ul><p>Ce qui importe ici, c’est de comprendre <em>pourquoi</em> le système a éprouvé des difficultés dans ces cas précis. Les échecs n’étaient pas dus à une défaillance de l’approche globale, mais à des limitations de composants spécifiques, tout particulièrement le modèle de vecteurs denses utilisé pour la recherche sémantique dans certains scénarios multilingues.</p><p>La recherche et le jugement étant clairement séparés, il n’est pas nécessaire de réécrire le système pour améliorer les performances. L’intégration d’un modèle d’embedding multilingue plus performant, l’enrichissement du contexte des entités ou l’affinement des stratégies de récupération amélioreraient les résultats dans ces catégories sans modifier l’architecture de noyau.</p><p>Du point de vue architectural, c’est le véritable indicateur de réussite.</p><h2>Ce que ces résultats nous révèlent sur l&#x27;architecture du système</h2><p>Si l&#x27;on considère l&#x27;ensemble de la série, quelques tendances se dégagent :</p><ul><li><strong>La préparation est plus importante qu&#x27;une combinaison intelligente. </strong>L’enrichissement des entités avec leur contexte dès le départ réduit considérablement l’ambiguïté par la suite.</li><li><strong>Les LLM sont bien plus précieux en tant que juges qu’en tant qu’outils de recherche. </strong>Leur demander d&#x27;expliquer <em>pourquoi</em> une correspondance est logique est bien plus efficace que de leur demander de rechercher.</li><li><strong>La fiabilité permet la précision. </strong>L&#x27;appel de fonction n&#x27;a pas seulement nettoyé le JSON ; il a débloqué la récupération qui était déjà latente dans l&#x27;étape de récupération.</li><li><strong>La généralisation l’emporte sur la spécialisation. </strong>Un petit nombre d’abstractions bien choisies a permis de gérer des dizaines de types de défis sans avoir recours à une logique personnalisée.</li></ul><p>Cette approche explique pourquoi le prototype s’appuie nativement sur Elasticsearch tout en limitant l’usage des modèles de langage à une stricte nécessité. L’objectif n’est pas de se substituer aux moteurs de recherche classiques, mais d’apporter une couche d’explication quand la compréhension du contexte est cruciale.</p><h2>Conclusions</h2><p>L’enjeu final n’était pas d’atteindre des statistiques idéales, mais de s’attaquer à une interrogation bien plus essentielle :</p><p><em>Une architecture transparente, axée sur rechercher et assistée par LLM, peut-elle gérer l&#x27;ambiguïté des entités du monde réel sans s&#x27;effondrer en règles ou en boîtes noires ?</em></p><p>Pour ce prototype pédagogique, la réponse est oui, avec des réserves explicites concernant la mise en production, la conformité, la surveillance et la qualité des données. Si vous concevez des systèmes devant justifier <em>pourquoi</em> une correspondance d’entités a été établie, ce modèle mérite une attention toute particulière. J’espère que cette série de publications a démontré que la résolution d’entités n’a rien d’un processus mystérieux. Avec une séparation adéquate des responsabilités, la résolution d’entités devient un processus que l’on peut analyser, mesurer et améliorer.</p><p>Ce travail suggère également un modèle d’architecture plus large. On voit apparaître ici un glissement méthodologique important par rapport à l’architecture RAG classique. Au lieu de laisser la recherche alimenter directement la génération, nous introduisons une étape d’évaluation explicite. Le LLM est d’abord utilisé pour juger et vérifier la pertinence des candidats récupérés, et seuls les résultats approuvés sont autorisés à enrichir la génération. Vous pouvez voir cela comme un « Generation-Augmented Retrieval-Augmented Generation with Evaluation », ou GARAGE, parce que tout le monde adore les bons acronymes.</p><p>Quels autres cas d&#x27;utilisation pourraient bénéficier de ce modèle ? Les systèmes exigeant de la confiance, de la transparence et un raisonnement défendable sont des candidats naturels pour ce modèle. Les travaux futurs dans ce domaine s’annoncent tout aussi passionnants que les résultats présentés ici, et j’ai hâte de voir comment la communauté s’en emparera pour la suite.</p><h2>Prochaines étapes : À vous de jouer</h2><p>Envie de voir comment ce système relève le défi le plus complexe ? Consultez le <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,05_ultimate_challenge_v3.ipynb,-Initial%20public%20lab"><strong>carnet de notes Ultimate Challenge</strong></a> pour une présentation complète avec des implémentations réelles, des explications détaillées et des exemples pratiques.</p><p>Le pipeline complet de résolution d&#x27;entités démontre les concepts fondamentaux et l&#x27;architecture nécessaires à une utilisation en production. Cette structure sert de fondation pour bâtir des outils de veille médiatique capables d’identifier des entités et de justifier chaque correspondance, garantissant ainsi la traçabilité des informations extraites.<br/></p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/hnsw-elasticsearch-adaptive-early-termination</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/hnsw-elasticsearch-adaptive-early-termination</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tommaso Teofili]]></dc:creator>
            <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6d3186f609dd56dc5ffe33d70fa9e5cfa05b51f-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch utilise l&#x27;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&#x27;effectue par expansion itérative des nodes candidats dans le graphe, en conservant un ensemble limité des voisins les plus proches découverts jusqu&#x27;à 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&#x27;optimiser le parcours du graphe HNSW est d&#x27;interrompre la recherche lorsque la probabilité marginale de trouver de nouveaux les plus proches n&#x27;augmente plus. C&#x27;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&#x27;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&#x27;arrêt précoce dans HNSW afin de mieux l&#x27;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&#x27;à présent, jusqu&#x27;à avoir exploré l&#x27;ensemble du graphe ou répondu à certains critères d&#x27;arrêt précoce.</p><p>L&#x27;arrêt précoce n&#x27;est donc pas toujours une optimisation ; il <strong>fait partie intégrante de l&#x27;algorithme de recherche lui-même</strong>. Le moment où nous décidons d&#x27;interrompre la recherche détermine l&#x27;équilibre entre l&#x27;efficacité et le rappel. Dans Elasticsearch, il existe déjà plusieurs façons d&#x27;interrompre prématurément une requête sur HNSW :</p><ul><li>Un nombre maximal déterminé de nodes est exploré.</li><li>Un délai d&#x27;expiration déterminé est atteint.</li></ul><p>Bien que simples et prévisibles, ces règles sont largement <strong>indépendantes de ce qu&#x27;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&#x27;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&#x27;é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&#x27;arrêt précoce sur le <strong>progrès plutôt que sur l&#x27;effort</strong>.</p><p>Au lieu de demander :</p><p>&quot;Combien d&#x27;étapes avons-nous franchies ?&quot;</p><p>La nouvelle question devient :</p><p>« Quelle est la quantité de calcul que nous acceptons de gaspiller, jusqu&#x27;à ce que nous perdions espoir ? »</p><p>Lors d&#x27;une recherche HNSW, l&#x27;exploration précoce génère généralement des améliorations maximales de l&#x27;ensemble des k meilleurs candidats. Au cours des premières étapes de l&#x27;exploration du graphe HNSW, l&#x27;ensemble des voisins est mis à jour en continu à mesure que l&#x27;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&#x27;<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&#x27;exploration du graphe HNSW, nous calculons également le taux de saturation de la file d&#x27;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&#x27;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&#x27;exploration du graphe est interrompue.</p><p>D&#x27;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&#x27;avantages.</p><p>Ce recadrage est puissant car il lie directement l&#x27;arrêt aux <em>résultats observables</em> plutôt qu&#x27;à des limites fixes arbitraires.</p><p>L&#x27;avantage de cette technique d&#x27;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&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a9d07c5153ea64a2426c82487c36846030692bb9-1600x945.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dac56125c4b16d1b596c9876b6ca9ac7b2dc87fa-1600x944.png" style="width: 100%;" /><h2><strong>Seuils statiques et dynamiques HNSW</strong></h2><p>Dans Elasticsearch, cela se traduit concrètement par l&#x27;utilisation de <strong>seuils statiques</strong>. Le premier seuil correspond au <strong>seuil de saturation</strong>, c&#x27;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&#x27;attente sous-optimale, soit le <strong>seuil de patience</strong>.</p><p>Lors de l&#x27;introduction de cette stratégie d&#x27;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&#x27;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&#x27;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>Une surexploration pour les requêtes simples.</li><li>Un arrêt prématuré pour les requêtes complexes.</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&#x27;arrêt précoce de HNSW adaptatif</strong></h2><p>L&#x27;arrêt précoce adaptatif aborde ce problème sous un angle différent. Au lieu d&#x27;imposer des seuils d&#x27;arrêt prédéfinis, l&#x27;algorithme <strong>détermine le moment où il doit s&#x27;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&#x27;attente entre deux candidats consécutifs, nous avons décidé d&#x27;introduire à la fois un taux de découverte lissé instantané $d_{q,i} $ (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&#x27;une moyenne glissante $\mu_{q,i}$ et un écart-type $\sigma_{q,i}$ d&#x27;un tel taux de découverte pendant la visite du graphe (en utilisant l&#x27;<a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford&#x27;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&#x27;entre elles.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1958be7ca4425ade66eaf621ada3533173183598-694x118.png" style="width: 100%;" /><p>Les seuils auparavant statiques deviennent adaptatifs aux statistiques du taux de découverte : le seuil de saturation devient la moyenne mobile plus l&#x27;écart type, tandis que nous faisons en sorte que la patience s&#x27;adapte et évolue inversement avec l&#x27;écart type.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f7ee4a55c24853b657df26052b275e8bd76cf0f9-654x156.png" style="width: 100%;" /><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&#x27;arrête si la saturation persiste pendant un nombre d&#x27;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&#x27;une sortie anticipée) et qui s&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2a906997d9a25d74c7038bd9661bc97581e7258e-1600x938.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a114911e232c351dbb814cea20e8b0f1415a717-1600x925.png" style="width: 100%;" /><p>L&#x27;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&#x27;index</a>).</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Langage d'expression commun (CEL) : comment l'entrée CEL améliore la collecte de données dans les intégrations Elastic Agent]]></title>
            <description><![CDATA[Découvrez en quoi le Common Expression Language se distingue des autres langages de programmation, comment nous l’avons étendu pour l’entrée CEL de Filebeat, et la flexibilité qu’il vous offre pour exprimer la logique de collecte de données dans les intégrations Elastic Agent.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/common-expression-language-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/common-expression-language-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Berkhout]]></dc:creator>
            <pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/985c50bfabee3348494eb4307f0b3375a97a0644-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Les <a href="https://www.elastic.co/integrations">intégrations</a> Elastic Agent permettent d’ingérer des données dans Elasticsearch à partir d’un large éventail de sources. Elles regroupent la logique de collecte, les pipelines d’ingestion, les tableaux de bord et d’autres artefacts au sein d’un package installable et administrable depuis l’interface web Kibana.</p><p>Les intégrations configurent une ou plusieurs <a href="https://www.elastic.co/docs/reference/beats/filebeat/configuration-filebeat-options">entrées Filebeat</a> pour assurer la collecte des données. Pour collecter des données via des API HTTP, nous avons souvent utilisé l’entrée <a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-httpjson">HTTP JSON</a>. Cependant, même les API de type listing les plus simples peuvent varier considérablement dans leurs détails. Le modèle de transformations configurées en YAML de l’entrée HTTP JSON peut alors devenir contraignant, voire parfois insuffisant pour exprimer la logique de collecte requise.</p><p>L’<a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel">entrée Common Expression Language (CEL)</a> a été introduite afin de permettre une interaction plus souple avec les API HTTP. <a href="https://cel.dev/">CEL</a> est un langage conçu pour être intégré dans des applications nécessitant un moyen rapide, sûr et extensible d’exprimer des conditions et des transformations de données. L’entrée CEL permet au créateur d’intégration d’écrire une expression unique capable de lire les paramètres, de suivre son propre état, d’effectuer des requêtes, de traiter les réponses et, au final, de renvoyer des événements prêts à être ingérés.</p><p>Dans cet article, nous examinons les différences entre CEL et d’autres langages de programmation, les extensions apportées pour l’entrée CEL, ainsi que la puissance et la souplesse qu’il apporte à l’expression de votre logique de collecte de données.</p><h2>CEL et son fonctionnement dans l’entrée</h2><p>CEL est un langage d’expressions. Il ne comporte pas d’instructions. Lorsque vous écrivez en CEL, vous ne décrivez pas une suite d’actions à exécuter à l’aide d’instructions. Vous indiquez plutôt la valeur à produire en rédigeant une expression. Chaque expression CEL renvoie une valeur. De petites expressions peuvent être combinées pour former une expression plus large, capable de produire un résultat selon des règles plus complexes. Nous verrons plus loin comment utiliser des expressions pour des usages généralement exprimés à l’aide d’instructions dans d’autres langages.</p><p>CEL est volontairement un langage non Turing-complet. Il n’autorise pas les boucles non bornées. Nous verrons également comment traiter des listes et des maps à l’aide de macros. En évitant les boucles non bornées, le langage garantit un temps d’exécution prévisible et limité pour chaque expression.</p><p>L’entrée CEL est configurée avec un programme CEL (une expression) et un état initial. L’état est fourni en entrée au programme. Le programme est évalué afin de produire un état de sortie. Si l’état de sortie comprend une liste d’événements, ceux-ci sont extraits puis publiés. Le reste de l’état de sortie est utilisé comme entrée pour l’évaluation suivante. Si l’état de sortie contient un ou plusieurs événements et que l’indicateur <code>want_more: true</code>, l’évaluation suivante est effectuée immédiatement ; sinon, l’entrée attend la fin de l’intervalle configuré avant de poursuivre. Voici un schéma simplifié du flux de contrôle de l’entrée :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42671541f97e2dba808fd53969fe12f517917f9a-1600x529.png" style="width: 100%;" /><p>La sortie de chaque évaluation est transmise comme entrée à l’évaluation suivante, tant que l’entrée s’exécute. Les données de sortie sous la clé « <code>cursor</code>» sont persistées sur disque et rechargées après le redémarrage de l’entrée, mais le reste de l’état n’est pas conservé entre les redémarrages.</p><p>Le langage CEL lui-même offre des fonctionnalités limitées et évite les effets de bord, mais il est extensible. L’implémentation <a href="https://github.com/google/cel-go">cel-go</a> ajoute certaines fonctionnalités, comme la prise en charge des syntaxes et des types optionnels. La bibliothèque <a href="https://github.com/elastic/mito">Mito</a> s’appuie sur cel-go et enrichit ses capacités, notamment en permettant l’exécution de requêtes HTTP. L’entrée CEL utilise la version de CEL fournie par Mito.</p><h2>Travailler avec Mito</h2><p>Pour créer ou déboguer une intégration à l’aide de l’entrée CEL, il est essentiel de comprendre l’état de sortie que votre programme CEL produira à partir d’un état d’entrée donné. Pendant le développement, il peut être contraignant d’exécuter votre programme CEL via l’entrée, au sein de l’ensemble de la Suite Elastic. Pour accélérer la boucle de rétroaction, vous pouvez utiliser l’outil de commande en ligne de Mito. Il vous permet d’exécuter un programme CEL directement et d’observer la sortie générée pour une entrée donnée.</p><p>Mito est écrit en Go et peut être installé comme suit :</p><pre><code>go install github.com/elastic/mito/cmd/mito@latest</code></pre><p>Lorsque vous exécutez un programme CEL avec Mito, vous lui fournissez généralement deux fichiers : un fichier JSON contenant l’état d’entrée initial, et un autre fichier avec le code source de votre programme CEL :</p><pre><code>mito -data state.json src.cel</code></pre><p>Pour faciliter le copier-coller, les exemples de cet article sont écrits sous forme de commandes uniques qui permettent au shell de créer des fichiers temporaires à la volée, en enveloppant le contenu de chaque fichier dans <code>&lt;(echo &#x27;...content...&#x27;)</code>. Dans votre propre développement, travailler avec des fichiers réels sera plus facile.</p><h2>Récupération des tickets depuis GitHub</h2><p>L&#x27;exemple suivant inclut un programme CEL complet qui récupérera des données sur les problèmes depuis l&#x27;<a href="https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues">API GitHub</a>. Son état d&#x27;entrée initial contient l&#x27;URL du point de terminaison de l&#x27;API et quelques informations sur la manière dont il doit gérer la pagination. Le programme CEL utilise les données dans l’état d’entrée pour générer une requête. Il va décoder la réponse, produire des événements à partir de celle-ci, et les renvoyer en tant que partie de son état de sortie.</p><pre><code>mito -data <(echo '
  {
    "url": "https://api.github.com/repos/elastic/integrations/issues",
    "per_page": 3,
    "max_pages": 3
  }
') <(echo '
  int(state.?cursor.page.orValue(1)).as(page,
    (
      state.url + "?" + {
        "state": ["all"],
        "sort": ["created"],
        "direction": ["asc"],
        "per_page": [string(state.per_page)],
        "page": [string(page)],
      }.format_query()
    ).as(full_url,
      request("GET", full_url).with({
        "Header": {
          "Accept": ["application/vnd.github+json"],
          "X-GitHub-Api-Version": ["2022-11-28"],
        }
      }).do_request().as(resp,
        resp.Body.decode_json().as(data,
          state.with({
            "events": data.map(i, {
              "html_url": i.html_url,
              "title": i.title,
              "created_at": i.created_at,
            }),
            "cursor": { "page": page + 1 },
            "want_more": size(data) == state.per_page && page < state.max_pages,
          })
        )
      )
    )
  )
')</code></pre><p>Sa première évaluation produit la sortie suivante :</p><pre><code>{
  "cursor": {
    "page": 2
  },
  "events": [
    {
      "created_at": "2018-09-14T09:47:35Z",
      "html_url": "https://github.com/elastic/integrations/issues/3250",
      "title": "Increase support of log formats in haproxy filebeat module"
    },
    {
      "created_at": "2019-02-06T12:37:37Z",
      "html_url": "https://github.com/elastic/integrations/issues/487",
      "title": "ETCD Metricbeat module needs polishing and grooming"
    },
    {
      "created_at": "2019-08-13T11:33:11Z",
      "html_url": "https://github.com/elastic/integrations/pull/1",
      "title": "Initial structure"
    }
  ],
  "max_pages": 3,
  "per_page": 3,
  "url": "https://api.github.com/repos/elastic/integrations/issues",
  "want_more": true
}</code></pre><p>Les événements seront supprimés et, lorsqu’ils seront exécutés dans l’entrée CEL, ils seront publiés pour ingestion. Le reste de la sortie sera transmis à l’évaluation suivante du programme CEL en tant qu’état d’entrée.</p><p></p><p>Pour comprendre le fonctionnement de ce programme CEL, nous allons examiner quelques exemples CEL plus simples et détailler davantage le fonctionnement de l’entrée CEL.</p><h2>Les bases de CEL</h2><p>Dans le langage CEL, il n’y a pas d’instructions ; uniquement des expressions. Toute expression CEL valide est évaluée pour produire une valeur finale. Voici l’une des plus petites expressions CEL que vous puissiez écrire, ainsi que sa sortie :</p><pre><code>mito <(echo '
  "hello" + " " + "world"
')</code></pre><pre><code>"hello world"</code></pre><p>De nombreuses expressions simples sont intuitives. Les opérations mathématiques ne sont prises en charge que sur des valeurs de même type (par exemple, <code>int</code> avec <code>int</code>), convertissez donc les types selon vos besoins (ici de <code>int</code> à <code>double</code>) :</p><pre><code>mito <(echo '
  double((1 + 2) * (3 + 4)) / 2.0
')</code></pre><pre><code>10.5</code></pre><p>Il n’y a pas de variables dans le langage CEL, mais une expression peut recevoir un nom et être utilisée dans une expression plus large grâce à la macro <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-As__Macro_-Collections"><code>as</code></a> de Mito. Dans cet exemple, l’expression <code>(1 + 1)</code> évalue la valeur <code>2</code>, et <code>.as(n, ...)</code> donne à cette valeur le nom <code>n</code> pour l’utilisation dans l’expression <code>&quot;one plus one is &quot;+string(n)</code>:</p><pre><code>mito <(echo '
  (1 + 1).as(n, "one plus one is "+string(n))
')</code></pre><pre><code>"one plus one is 2"</code></pre><p>Il est également possible d’accumuler des informations dans une carte et de les utiliser plus tard dans l’expression, comme démontré ici avec <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-With-Collections"><code>with</code></a>:</p><pre><code>mito <(echo '
  { "key": "value" }.with({ "key2": "value2" }).as(data,
    {
      "data": data,
      "size": size(data),
    }
  )
')</code></pre><pre><code>{
  "data": {
    "key": "value",
    "key2": "value2"
  },
  "size": 2
}</code></pre><p>Regardez à nouveau cet exemple. Remarquez que la partie imbriquée, <code>({ &quot;data&quot;: data, &quot;size&quot;: size(data), })</code>, nous donne la forme de la valeur finale. C&#x27;est une carte avec les clés <code>&quot;data&quot;</code> et <code>&quot;size&quot;</code>. Les valeurs de ces clés dépendent de <code>data</code>, qui est défini par la partie extérieure de l’expression. Lire les expressions CEL de l&#x27;intérieur vers l&#x27;extérieur peut aider à voir rapidement ce qu&#x27;elles renverront.</p><p>CEL ne possède pas d’instructions de flux de contrôle, comme <code>if</code>, mais le branchement conditionnel peut être réalisé avec l’opérateur ternaire :</p><pre><code>mito <(echo '
  1 + 1 < 12 ? "few" : "many"
')</code></pre><pre><code>"few"</code></pre><p>Les boucles non bornées et la récursion ne sont pas prises en charge, car CEL n’est pas un langage Turing-complet. Le temps d’exécution est donc prévisible et proportionnel à la taille des données d’entrée et à la complexité de l’expression.</p><p>Bien que les boucles non bornées ne soient pas possibles dans des expressions CEL individuelles, vous pouvez traiter des listes et des cartes à l’aide de macros comme <a href="https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros"><code>map</code></a> :</p><pre><code>mito <(echo '
  [1, 2, 3].map(x, x * 2)
')</code></pre><pre><code>[2, 4, 6]</code></pre><p>Dans cette section, nous avons abordé les points suivants :</p><ul><li>Les chaînes de caractères, les nombres, les listes et les maps.</li><li>La concaténation de chaînes.</li><li>Les opérations mathématiques.</li><li>Le transtypage.</li><li>Les conditions.</li><li>La nomination des sous-expressions.</li><li>Le traitement des collections.</li></ul><p>Ensuite, nous verrons comment effectuer des requêtes HTTP.</p><h2>Requêtes</h2><p>Mito étend CEL en lui donnant la possibilité d&#x27;effectuer des <a href="https://pkg.go.dev/github.com/elastic/mito/lib#HTTP">requêtes HTTP</a> :</p><pre><code>mito <(echo '
  get("https://example.com").as(resp, string(resp.Body))
')</code></pre><pre><code>"<!doctype html><html lang=\"en\"><head><title>Example Domain</title>..."</code></pre><p>Les requêtes peuvent être construites explicitement avant leur exécution. Cela permet d’utiliser différentes méthodes HTTP et d’ajouter des en-têtes ainsi qu’un corps de requête.</p><p>Dans cet exemple, nous construisons une URL avec l’aide de <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-Format_Query-HTTP"><code>format_query</code></a>, ajoutons un en-tête à la requête, et analysons le corps de la réponse avec <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-Decode_JSON-JSON"><code>decode_json</code></a>. Lorsque l&#x27;option <code>-log_requests</code> est sélectionnée, Mito log des informations détaillées au format JSON sur chaque demande et réponse.</p><pre><code>mito -log_requests <(echo '
  request("GET",
    "https://postman-echo.com/get?" + {
        "q": ["query value"]
     }.format_query()
  ).with({
    "Header": { "Accept": ["application/json"] }
  }).do_request().as(resp, {
    "status": resp.StatusCode,
    "data": resp.Body.decode_json(),
  })
')</code></pre><pre><code>{"time":"...","level":"INFO","msg":"HTTP request",...}
{"time":"...","level":"INFO","msg":"HTTP response",...}
{
  "data": {
    "args": {
      "q": "query value"
    },
    "headers": {
      "accept": "application/json",
      "accept-encoding": "gzip, br",
      "host": "postman-echo.com",
      "user-agent": "Go-http-client/2.0",
      "x-forwarded-proto": "https"
    },
    "url": "https://postman-echo.com/get?q=query+value"
  },
  "status": 200
}</code></pre><h2>Gestion de l’état et des évaluations</h2><p>Maintenant que nous avons vu comment effectuer des requêtes et passé en revue les bases de CEL nécessaires pour produire l’état de sortie souhaité, examinons de plus près ce que nous devons placer dans l’état de sortie et comment cela nous permet d’orienter les traitements ultérieurs.</p><p>Le programme CEL d&#x27;une intégration doit s&#x27;assurer que son état de sortie peut être utilisé comme entrée de l&#x27;évaluation suivante. La configuration définit l&#x27;état initial, qui doit être répété dans la sortie avec toutes les modifications appropriées. Une façon simple de le faire est d’utiliser <code>state.with({ ... })</code>, pour répéter la carte d’état avec quelques dérogations. Un modèle courant pour les petits programmes consiste à envelopper l&#x27;ensemble du programme dans <code>state.with()</code>, de sorte que la propagation de l&#x27;état ne doive pas être répétée dans chaque branche qui génère des données de sortie (par exemple, succès, erreurs).</p><p>Lorsque des valeurs d’état sont initialisées par une évaluation plutôt que codées en dur dans l’état d’entrée initial, le programme devra vérifier la présence d’une valeur existante avant de définir la valeur initiale. La prise en charge de la <a href="https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes">syntaxe et des types optionnels</a> peut aider à résoudre ce problème. En utilisant un point d&#x27;interrogation avant le nom du champ dans une clé de carte, l&#x27;accès devient facultatif : il peut ou non aboutir à une valeur, mais d&#x27;autres accès facultatifs sont possibles et il est facile de fournir une valeur par défaut si aucune valeur n&#x27;est présente :<br/></p><pre><code>mito -data <(echo '{}') <(echo '
  int(state.?counter.orValue(0)).as(counter,
    state.with({
      "counter": counter + 1,
      "want_more": counter + 1 < 3,
    })
  )
')</code></pre><pre><code>{ "counter": 1, "want_more": true }
{ "counter": 2, "want_more": true }
{ "counter": 3, "want_more": false }</code></pre><p>Dans cet exemple, la valeur du compteur lue à partir de l&#x27;état est convertie en <code>int</code> car tous les nombres sont sérialisés dans l&#x27;état sous forme de nombres à virgule flottante, conformément aux conventions établies par JSON et le type <code>Number</code> de JavaScript. Il convient également de noter que <code>&quot;want_more&quot;: true</code> est respecté ici par Mito, mais lorsqu’elle est exécutée dans l’entrée CEL, l’évaluation ne sera répétée que si la sortie contient également des événements.</p><p>C’est une exigence des programmes CEL exécutés par l’entrée CEL de retourner une clé <code>&quot;events&quot;</code> dans leur carte de sortie. Sa valeur peut être une liste de cartes d’événements, une liste vide ou une carte d’événement unique. Le cas d’événement unique est généralement utilisé pour les erreurs. L’événement sera publié par l’entrée, mais sa valeur sera également journalisée, et s’il définit une valeur <code>error.message</code>, celle-ci sera utilisée pour mettre à jour l’état de santé de la Fleet de l’intégration. Si votre programme ne produit qu’un seul événement sans erreur, il est préférable de l’inclure dans une liste.</p><p>Reprenons la sortie de notre programme de récupération des tickets GitHub présenté précédemment :</p><pre><code>{
  "url": "https://api.github.com/repos/elastic/integrations/issues",
  "per_page": 3,
  "max_pages": 3,
  "cursor": {
    "page": 2
  },
  "events": [
    { ... },
    { ... },
    { ... }
  ],
  "want_more": true
}</code></pre><p>Le programme gérait effectivement son état de la manière suivante :</p><ul><li>Répétition des valeurs d’état initiales dans <code>url</code>, <code>per_page</code>, et <code>max_pages</code>.</li><li>Ajout d’état qui devrait être maintenu lors des redémarrages dans <code>cursor.page</code>.</li><li>Les événements prêts à être publiés dans la liste <code>events</code>.</li><li>Demande de réévaluation immédiate avec <code>want_more: true</code>.</li></ul><p>Maintenant que vous maîtrisez l’accès optionnel, la gestion de l’état, les bases de CEL et les requêtes HTTP, le programme complet de récupération des tickets GitHub devrait être plus clair. Essayez de l’exécuter avec Mito et d’expérimenter quelques modifications.</p><h2>Conclusion et ressources</h2><p>Dans cet article, nous avons expliqué ce qu’est le langage CEL et comment il a été étendu dans la bibliothèque Mito pour une utilisation dans l’entrée CEL. Nous avons illustré la flexibilité de CEL à travers un programme exemple qui récupère des informations sur des tickets via l’API GitHub, et détaillé les éléments nécessaires à sa compréhension : accès aux paramètres dans l’état initial, interaction avec les API HTTP, renvoi d’événements destinés à l’ingestion et gestion de l’état pour les exécutions ultérieures du programme.</p><p>Pour aller plus loin et créer des intégrations à l’aide de l’entrée CEL, plusieurs ressources méritent votre attention :</p><ul><li><a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel">Entrée CEL - Documentation Filebeat</a></li><li><a href="https://pkg.go.dev/github.com/elastic/mito">Documentation Mito</a></li><li><a href="https://cel.dev/">Langage d&#x27;expression commun - site web cel.dev</a></li><li><a href="https://www.elastic.co/docs/extend/integrations">Créer une intégration - Documentation Elastic</a></li></ul><p>Et sans doute la ressource la plus précieuse pour créer des intégrations avec l’entrée CEL reste le code CEL des intégrations Elastic existantes, disponible sur GitHub :</p><p><a href="https://github.com/search?q=repo%3Aelastic%2Fintegrations+path%3A**%2Fcel.yml.hbs&amp;type=code"><code>cel.yml.hbs</code> fichiers du dépôt des intégrations Elastic – GitHub</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Résolution d’entités avec Elasticsearch et les LLM, partie 2 : mise en correspondance d’entités avec le jugement des LLM et la recherche sémantique]]></title>
            <description><![CDATA[Utiliser la recherche sémantique et le jugement transparent des LLM pour la résolution d’entités dans Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-entity-resolution-llm-semantic-search</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-entity-resolution-llm-semantic-search</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
            <pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/473ca4357c7d60f690edbd2a844acda169aca9c3-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans<a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch"> la Partie 1</a>, nous avons préparé notre liste de surveillance et extrait les mentions d&#x27;entités. Nous sommes maintenant prêts à répondre à la question clé : à quelle entité une mention renvoie-t-elle réellement ? Revenons à l’exemple présenté dans le premier article de cette série, qui expliquait pourquoi nous avons besoin de la résolution d’entités : « The Swift update is here ! » Imaginons que ce titre soit accompagné d’un peu plus de contexte :</p><ol><li>La nouvelle mise à jour de Swift est arrivée ! Les développeurs sont impatients de tester les nouvelles fonctionnalités.</li><li>La nouvelle mise à jour de Swift est arrivée ! Le nouvel album sortira le mois prochain.</li></ol><p>Avec ce contexte supplémentaire, nous devrions être en mesure d’associer le nom « Swift » à la bonne entité.</p><p>Dans l&#x27;<a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">article précédent</a>, nous avons constitué notre liste de surveillance et enrichi les entités avec un contexte supplémentaire. En reprenant nos exemples ci-dessus, nous devons disposer au minimum des deux entités suivantes dans la liste : Taylor Swift et le langage de programmation Swift. Nous avons également expliqué comment extraire les mentions d’entités à partir d’un texte. Dans ces deux exemples, la mention extraite serait « Swift ». Avec ces éléments en place — la liste de surveillance enrichie et les entités extraites — nous sommes enfin prêts à introduire la vedette du moment : la mise en correspondance des entités.</p><p><strong>Rappel :</strong> il s’agit d’un prototype pédagogique conçu pour illustrer les concepts de mise en correspondance d’entités. En production, les systèmes peuvent utiliser différents grands modèles de langage (LLM), des règles de correspondance personnalisées, des pipelines d’évaluation spécialisés ou encore des approches d’ensemble combinant plusieurs stratégies de correspondance.</p><h2>Le problème : pourquoi la mise en correspondance est complexe</h2><p>Le langage humain est une chose remarquable. L’une de ses caractéristiques les plus intéressantes est sa créativité sans fin. Nous pouvons générer et comprendre un nombre infini de nouvelles phrases. Dès lors, est-il surprenant que les correspondances exactes soient rares en résolution d’entités ? Les auteurs s’efforcent d’être créatifs dès qu’ils le peuvent. Il serait vite fastidieux de devoir écrire et lire les noms complets chaque fois qu’une entité est mentionnée. Ainsi, si les correspondances exactes sont simples, la réalité est que nous avons besoin d’une approche plus sophistiquée de la résolution d’entités : une approche suffisamment robuste pour gérer au moins une partie de la créativité sans limite des auteurs humains. C’est pourquoi nous décomposons le problème en deux étapes : utiliser Elasticsearch pour récupérer des candidats plausibles à grande échelle, puis recourir à un LLM pour déterminer si ces candidats renvoient réellement à la même entité du monde réel.</p><h2>La solution : une mise en correspondance en trois étapes avec un jugement LLM transparent</h2><p>Nous vivons un changement de paradigme dans notre manière d’utiliser les ordinateurs. Tout comme l’essor d’Internet nous a fait passer d’une informatique localisée à un réseau mondialement connecté, l’IA générative transforme en profondeur la façon dont le contenu, le code et l’information sont créés. En réalité, le prototype pédagogique qui accompagne cette série a été presque entièrement « vibe codé » à l’aide d’un LLM, avec des instructions soigneusement rédigées par l’auteur. Cela ne signifie pas que les LLM atteignent — ou atteindront — le niveau de productivité propre au langage humain, mais cela veut dire que nous disposons désormais d’une ressource puissante pour faciliter la résolution d’entités.</p><p>Un schéma courant avec l&#x27;IA générative est la génération augmentée par récupération (RAG). Ici, <em>récupération</em> signifie que l’on récupère des candidats d’entités (et non que l’on génère des réponses), et que le LLM est utilisé exclusivement pour évaluer les correspondances et en expliquer la logique. Bien que je <em>puisse</em> demander à un LLM de prendre en charge l’ensemble du processus de résolution d’entités, de bout en bout, cette approche serait coûteuse, tant en temps qu’en ressources financières. La RAG aide les LLM à accomplir leur tâche en leur fournissant du contexte de manière plus efficace, ce qui leur permet de contribuer plus efficacement à la résolution d’entités.</p><p>Pour la partie récupération de la RAG, nous faisons à nouveau appel à Elasticsearch. Nous identifions d’abord des correspondances potentielles en combinant la correspondance exacte, la correspondance sur des alias et la recherche hybride, qui associe recherche par mots-clés et recherche sémantique. Une fois ces correspondances potentielles identifiées, nous les transmettons à un LLM pour évaluation. Le LLM agit comme évaluateur final des correspondances. Nous demandons également au LLM d’expliquer son raisonnement, un élément différenciant important par rapport à d’autres systèmes de résolution d’entités. Sans ces explications, la résolution d’entités reste une boîte noire ; avec elles, nous pouvons comprendre pourquoi une correspondance est pertinente.</p><h2>Concepts clés : mise en correspondance en trois étapes, recherche hybride et jugement LLM transparent</h2><p><strong>Qu’est-ce que la mise en correspondance en trois étapes ?</strong> Au début de ce projet, nous avons émis l’hypothèse que la recherche sémantique jouerait un rôle clé dans le système, mais toutes les correspondances ne nécessitent pas un niveau de recherche aussi sophistiqué. Afin de trouver des correspondances efficacement, nous adoptons une approche progressive du problème. Tout d’abord, nous vérifions les correspondances exactes à l’aide de la recherche par mots-clés. Si nous trouvons une telle correspondance, le travail est terminé et nous pouvons passer à l’étape suivante. Si la correspondance exacte échoue, nous passons à la correspondance par alias. Dans le prototype, la correspondance par alias est également effectuée à l’aide d’une correspondance exacte sur des mots-clés, par souci de simplicité. En production, cette étape peut être enrichie par des règles de normalisation, de translittération, de correspondance approximative (fuzzy matching) ou par des tables d’alias maintenues. Si, après ces deux premières étapes, aucune correspondance potentielle n’a été trouvée, nous faisons appel à la recherche sémantique via la recherche hybride d’Elasticsearch, utilisant la méthode Reciprocal Rank Fusion (RRF).</p><p><strong>Qu’est-ce que la recherche hybride ?</strong> Dans Elasticsearch, nous pouvons utiliser la recherche sémantique pour identifier des correspondances pertinentes en tenant compte du contexte. Elasticsearch est largement utilisé pour la recherche vectorielle et la récupération hybride. La similarité sémantique est puissante pour capter le sens, mais elle ne remplace pas le filtrage structuré (par exemple, par plages temporelles, emplacements ou identifiants). Elle est souvent inutile lorsqu’une correspondance exacte est disponible. Elasticsearch s’est d’abord imposé grâce à la recherche lexicale, particulièrement efficace lorsque la recherche sémantique n’est pas adaptée. Pour tirer pleinement parti des deux approches, nous combinons la recherche lexicale et la recherche sémantique au sein d’une requête hybride unique. Nous fusionnons ensuite les résultats afin d’identifier les correspondances les plus probables à l’aide de la méthode RRF. Dans le prototype, les deux premiers résultats deviennent des correspondances potentielles pouvant être soumises à l’évaluation du LLM.</p><p><strong>Pourquoi faire appel au jugement LLM ?</strong> Les jugements et explications fournis par le LLM permettent à notre système de gérer l’ambiguïté et le contexte de manière transparente. C’est essentiel pour des cas comme « le président », qui peut désigner plusieurs entités selon le contexte. Cela permet également de gérer efficacement les surnoms et les variations culturelles. Enfin, lorsque nous traitons des tâches critiques — comme l’identification d’entités figurant sur des listes de sanctions — il est indispensable de comprendre pourquoi une correspondance a été acceptée afin de pouvoir faire confiance au système. Point essentiel : le LLM ne parcourt pas l’intégralité du corpus. Il évalue uniquement le petit ensemble de candidats renvoyé par Elasticsearch.</p><h2>Résultats concrets : mise en correspondance avec raisonnement du LLM</h2><p>Un défi majeur pour toute tâche de traitement automatique du langage naturel est la création d’un document de référence, une « answer key » indiquant quels sont les résultats attendus. Sans cela, il est quasiment impossible d’évaluer la performance d’un système sur une tâche donnée. Or, la création d’un tel document peut s’avérer laborieuse. Pour le prototype de résolution d’entités, nous avons de nouveau fait appel à l&#x27;IA générative afin de générer des données sur lesquelles nous pourrions effectuer des tests.</p><p>Nous avons d’abord défini plusieurs types de défis, comme les surnoms et la translittération, puis demandé au LLM de créer une collection hiérarchisée de jeux de données, devenant progressivement plus volumineux et plus complexes pour le système. La création des jeux de données s’est révélée moins simple qu’on aurait pu l’espérer. Le LLM avait une forte tendance à « tricher » en rendant la bonne réponse trop facile à trouver. Par exemple, l’un des types de défis portait sur le contexte sémantique. Ce type incluait des cas tels que faire correspondre « auteur russe » à « Leo Tolstoy ». Le LLM a incorrectement défini « auteur russe » comme un alias de « Leo Tolstoy », ce qui supprimait la nécessité d’une recherche hybride pour identifier la correspondance.</p><p>Après plusieurs refactorisations pour corriger ce type de problèmes, nous disposions de cinq niveaux d’ensembles de données. Les niveaux 1 à 4 devenaient progressivement plus volumineux et intégraient davantage de types de défis. Le niveau 5 constituait le « défi ultime », composé des exemples les plus complexes issus de tous les types de défis. L’ensemble des données de test est disponible dans un <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/comprehensive_evaluation">répertoire d’évaluation complet</a>.</p><p>Pour évaluer notre approche de résolution d’entités basée sur des prompts, nous avons concentré notre analyse sur le jeu de données de niveau 4. Il est important de noter que l’évaluation a été menée dans le cadre d’une expérience contrôlée afin de nous concentrer sur la qualité de mise en correspondance des entités. Les données de la liste de correspondances ont été préalablement enrichies avec du contexte, et les entités ont été extraites de l’article en amont. Cela a permis de s’assurer que l’évaluation se concentrait sur la correspondance plutôt que sur la précision de l’extraction. Cela isole la qualité de correspondance ; les performances de bout en bout dépendraient en outre du rappel d&#x27;extraction et de la qualité d&#x27;enrichissement.</p><h3>Ensemble de données d’évaluation</h3><p>L&#x27;ensemble de données d&#x27;évaluation de niveau 4 fournit un test complet des capacités du système : [1]</p><ul><li><strong>Entités de la liste de surveillance :</strong> 66 entités couvrant différents types (personnes, organisations, lieux).</li><li><strong>Articles de test :</strong> 69 articles couvrant des scénarios réels de résolution d’entités.</li><li><strong>Correspondances attendues :</strong> 206 correspondances attendues pour l&#x27;ensemble des articles.</li><li><strong>Types de défis : </strong>15 types de défis différents mettant à l&#x27;épreuve divers aspects de la résolution d&#x27;entités.</li></ul><p>Les types de défis inclus dans l’ensemble de données sont les suivants :</p><ul><li><strong>Surnoms :</strong> « Bob Smith » → « Robert Smith » (sept articles).</li><li><strong>Titres et titres honorifiques : « Dr. »</strong> Sarah Williams » → « Sarah Williams » (cinq articles).</li><li><strong>Contexte sémantique :</strong> « auteur russe » → « Leo Tolstoy » (huit articles).</li><li><strong>Noms multilingues :</strong> traitement des noms dans différentes écritures (six articles).</li><li><strong>Entités commerciales :</strong> variations de noms d’entreprises (sept articles).</li><li><strong>Références exécutives : </strong>« PDG de Microsoft » → « Satya Nadella » (cinq articles).</li><li><strong>Dirigeants politiques :</strong> références basées sur un titre (cinq articles).</li><li><strong>Initiales :</strong> « J. Smith » → « John Smith » (trois articles).</li><li><strong>Variations dans l’ordre des noms :</strong> différentes conventions d’ordre des noms (trois articles).</li><li><strong>Noms tronqués :</strong> correspondances partielles de noms (trois articles).</li><li><strong>Découpage des noms :</strong> noms séparés dans le texte (trois articles).</li><li><strong>Espaces ou tirets manquants :</strong> variations de mise en forme (deux articles).</li><li><strong>Translittération :</strong> correspondance de noms entre différents systèmes d’écriture (deux articles).</li><li><strong>Défis combinés :</strong> plusieurs défis dans un même article (six articles).</li><li><strong>Cas d’entreprise complexes :</strong> relations hiérarchiques entre entités commerciales (cinq articles).</li></ul><p>Examinons comment la résolution d’entités basée sur des prompts s’est comportée.</p><h3>Performance globale</h3><p>Les résultats montrent un fort potentiel pour l’évaluation des correspondances assistée par LLM, mais ils mettent également en évidence un problème significatif de fiabilité. Chaque paire candidate doit être évaluée par le LLM. Des erreurs dans la sortie structurée peuvent réduire la précision et le rappel, même lorsque la phase de récupération fonctionne correctement.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrique</td><td style="border: 1px solid #ccc; padding: 4px;">Valeur</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Précision</td><td style="border: 1px solid #ccc; padding: 4px;">83,8 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Rappel</td><td style="border: 1px solid #ccc; padding: 4px;">62,6 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Score F1</td><td style="border: 1px solid #ccc; padding: 4px;">71,7 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Nombre total de correspondances trouvées</td><td style="border: 1px solid #ccc; padding: 4px;">344</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Taux d'acceptation des LLM</td><td style="border: 1px solid #ccc; padding: 4px;">44,8 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Taux d'erreur</td><td style="border: 1px solid #ccc; padding: 4px;">30,2 %</td>
                    </tr>
              </tbody>
            </table>
          <h3>Le problème du taux d&#x27;erreur</h3><p>Rappelons que la première étape du prototype consiste à créer des paires de correspondances potentielles à l’aide d’Elasticsearch. Chacune de ces correspondances potentielles doit ensuite être évaluée par le LLM. Pour traiter efficacement l’ensemble de ces correspondances, nous regroupons les appels au LLM par lots. Cela réduit les coûts d’API et la latence, mais augmente également le risque d’obtenir un JSON mal formé en sortie. À mesure que la taille des lots augmente, le JSON devient plus long et plus complexe, ce qui accroît la probabilité que le LLM génère un JSON invalide. C’est de là que provient le taux d’erreur de 30 %. Dans cette évaluation, nous avons utilisé une taille de lot de cinq correspondances par requête. Même avec cette taille de lot conservatrice, nous constatons toujours des échecs d&#x27;analyse JSON, ce qui fausse considérablement les résultats de l&#x27;évaluation.</p><h2>Prochaine étape : optimiser l’intégration des LLM</h2><p>Maintenant que nous avons mis en correspondance des entités à l’aide de la recherche sémantique et du jugement d’un LLM, nous disposons d’un pipeline complet de résolution d’entités. Cependant, cette approche introduit un nouveau mode de défaillance : le jugement du modèle peut être correct, mais sa sortie inutilisable. Nous pouvons optimiser l’intégration du LLM afin d’améliorer la fiabilité et la rentabilité. Dans le prochain article, nous verrons comment utiliser le function calling pour produire une sortie structurée, garantissant une structure et un typage sûrs, tout en réduisant les erreurs et les coûts.</p><h2>Essayez par vous-même</h2><p>Envie de voir la mise en correspondance d’entités en action ? Consultez le <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,03_entity_matching_v3.ipynb,-Initial%20public%20lab">carnet de notes sur l&#x27;appariement des entités</a> pour une présentation complète avec des implémentations réelles, des explications détaillées et des exemples pratiques. Le carnet vous montre exactement comment faire correspondre les entités à l&#x27;aide de la recherche en trois étapes, de la recherche hybride avec RRF et du jugement raisonné basé sur le LLM.</p><p><strong>Rappel :</strong> il s’agit d’un prototype pédagogique conçu pour illustrer les concepts. Lors de la mise en œuvre d’un système en production, tenez compte de facteurs supplémentaires tels que la sélection du modèle, l’optimisation des coûts, les exigences en matière de latence, la validation de la qualité, la gestion des erreurs et la supervision — des aspects qui ne sont pas couverts dans ce prototype à visée pédagogique.</p><h2>Remarques</h2><ol><li>Ces ensembles de données sont synthétiques et conçus à des fins pédagogiques. Ils reflètent des défis réels, mais ne représentent aucun domaine de production spécifique.</li></ol>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/opensearch-vs-elasticsearch-filtered-vector-search</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/opensearch-vs-elasticsearch-filtered-vector-search</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fb7ee623925ca6696d643e437ce8efe5fe749079-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Pourquoi la vitesse de recherche est importante pour les agents IA et l&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5a52a07773f0942c2baa732dacfe782aac0f415-1600x683.png" style="width: 100%;" /><p>Pour l&#x27;ingénierie du contexte, la récupération n&#x27;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&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/54ac476ff20a3cf93484298c9ae47612c12fc110-800x417.png" style="width: 100%;" /><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&#x27;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>Filtres : en stock, livrable à l&#x27;utilisateur à son code postal, livraison avant vendredi, prix inférieur à 60 $, annonce valide</li><li>Requête vectorielle : « Sac à dos cabine ordinateur 15 pouces résistant à l’eau »</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>« sac à dos de voyage cabine compartiment ordinateur »</li><li>« sac à dos de trajet quotidien, résistant à l’eau, ordinateur 15 pouces »</li><li>« sac à dos de cabine léger »</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>Formulation concernant les matériaux et la résistance à l’eau</li><li>Dimensions et compatibilité du compartiment pour ordinateur portable</li><li>Modalités de retour et clauses de garantie</li><li>Options alternatives en cas de stock faible</li></ul><p>Ceci est l&#x27;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&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c50d1df00968cac18149a2799e6242fbe49b66a0-1600x990.png" style="width: 100%;" /><h4>Quelques informations clés</h4><ul><li><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</li><li>Rappel : Mesure du Rappel@100 pour cette configuration spécifique</li><li>Latence moyenne (ms) : Temps de réponse moyen de bout en bout par requête</li><li>Débit : requêtes par seconde (QPS)</li><li>Pourcentage de rappel : Amélioration relative du rappel pour Elasticsearch comparativement à OpenSearch ((Elasticsearch - OpenSearch) / OpenSearch)</li><li>Latence Xs : latence moyenne d&#x27;OpenSearch divisée par la latence moyenne d&#x27;Elasticsearch</li><li>Débit Xs : débit Elasticsearch divisé par le débit OpenSearch</li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Moteur</td><td style="border: 1px solid #ccc; padding: 4px;">`s_n_r_value`</td><td style="border: 1px solid #ccc; padding: 4px;">Rappel</td><td style="border: 1px solid #ccc; padding: 4px;">Latence moyenne (ms)</td><td style="border: 1px solid #ccc; padding: 4px;">Débit</td><td style="border: 1px solid #ccc; padding: 4px;">Rappel %</td><td style="border: 1px solid #ccc; padding: 4px;">Latence Xs</td><td style="border: 1px solid #ccc; padding: 4px;">Débit Xs</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_250_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,7704</td><td style="border: 1px solid #ccc; padding: 4px;">25</td><td style="border: 1px solid #ccc; padding: 4px;">534,75</td><td style="border: 1px solid #ccc; padding: 4px;">9,70 %</td><td style="border: 1px solid #ccc; padding: 4px;">2,28</td><td style="border: 1px solid #ccc; padding: 4px;">1,91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_250_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,7023</td><td style="border: 1px solid #ccc; padding: 4px;">57,08</td><td style="border: 1px solid #ccc; padding: 4px;">279,58</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,8577</td><td style="border: 1px solid #ccc; padding: 4px;">25,42</td><td style="border: 1px solid #ccc; padding: 4px;">524,14</td><td style="border: 1px solid #ccc; padding: 4px;">7,20 %</td><td style="border: 1px solid #ccc; padding: 4px;">2.4</td><td style="border: 1px solid #ccc; padding: 4px;">2</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,8001</td><td style="border: 1px solid #ccc; padding: 4px;">60,9</td><td style="border: 1px solid #ccc; padding: 4px;">262,12</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_750_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,8947</td><td style="border: 1px solid #ccc; padding: 4px;">29,67</td><td style="border: 1px solid #ccc; padding: 4px;">528,09</td><td style="border: 1px solid #ccc; padding: 4px;">5,72 %</td><td style="border: 1px solid #ccc; padding: 4px;">2,25</td><td style="border: 1px solid #ccc; padding: 4px;">2,21</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_750_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,8463</td><td style="border: 1px solid #ccc; padding: 4px;">66,76</td><td style="border: 1px solid #ccc; padding: 4px;">239,11</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9156</td><td style="border: 1px solid #ccc; padding: 4px;">29,65</td><td style="border: 1px solid #ccc; padding: 4px;">534,5</td><td style="border: 1px solid #ccc; padding: 4px;">4,66 %</td><td style="border: 1px solid #ccc; padding: 4px;">2,46</td><td style="border: 1px solid #ccc; padding: 4px;">2,44</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,8748</td><td style="border: 1px solid #ccc; padding: 4px;">72,88</td><td style="border: 1px solid #ccc; padding: 4px;">219,01</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9386</td><td style="border: 1px solid #ccc; padding: 4px;">31,84</td><td style="border: 1px solid #ccc; padding: 4px;">497,3</td><td style="border: 1px solid #ccc; padding: 4px;">3,38 %</td><td style="border: 1px solid #ccc; padding: 4px;">2,71</td><td style="border: 1px solid #ccc; padding: 4px;">2,68</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9079</td><td style="border: 1px solid #ccc; padding: 4px;">86,16</td><td style="border: 1px solid #ccc; padding: 4px;">185,4</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9507</td><td style="border: 1px solid #ccc; padding: 4px;">34,69</td><td style="border: 1px solid #ccc; padding: 4px;">457,2</td><td style="border: 1px solid #ccc; padding: 4px;">2,57 %</td><td style="border: 1px solid #ccc; padding: 4px;">2,98</td><td style="border: 1px solid #ccc; padding: 4px;">2,96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9269</td><td style="border: 1px solid #ccc; padding: 4px;">103,36</td><td style="border: 1px solid #ccc; padding: 4px;">154,55</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9582</td><td style="border: 1px solid #ccc; padding: 4px;">37,9</td><td style="border: 1px solid #ccc; padding: 4px;">418,43</td><td style="border: 1px solid #ccc; padding: 4px;">1,99 %</td><td style="border: 1px solid #ccc; padding: 4px;">3,28</td><td style="border: 1px solid #ccc; padding: 4px;">3,26</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9395</td><td style="border: 1px solid #ccc; padding: 4px;">124,29</td><td style="border: 1px solid #ccc; padding: 4px;">128,53</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_3000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9636</td><td style="border: 1px solid #ccc; padding: 4px;">41,86</td><td style="border: 1px solid #ccc; padding: 4px;">379,4</td><td style="border: 1px solid #ccc; padding: 4px;">1,62 %</td><td style="border: 1px solid #ccc; padding: 4px;">3,46</td><td style="border: 1px solid #ccc; padding: 4px;">3,44</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_3000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9482</td><td style="border: 1px solid #ccc; padding: 4px;">144,67</td><td style="border: 1px solid #ccc; padding: 4px;">110,34</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_4000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9705</td><td style="border: 1px solid #ccc; padding: 4px;">50,28</td><td style="border: 1px solid #ccc; padding: 4px;">316,21</td><td style="border: 1px solid #ccc; padding: 4px;">1,06 %</td><td style="border: 1px solid #ccc; padding: 4px;">3,87</td><td style="border: 1px solid #ccc; padding: 4px;">3,85</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_4000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9603</td><td style="border: 1px solid #ccc; padding: 4px;">194,36</td><td style="border: 1px solid #ccc; padding: 4px;">82,22</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_5000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9749</td><td style="border: 1px solid #ccc; padding: 4px;">58,77</td><td style="border: 1px solid #ccc; padding: 4px;">270,91</td><td style="border: 1px solid #ccc; padding: 4px;">0,73 %</td><td style="border: 1px solid #ccc; padding: 4px;">4,43</td><td style="border: 1px solid #ccc; padding: 4px;">4,41</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_5000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9678</td><td style="border: 1px solid #ccc; padding: 4px;">260,33</td><td style="border: 1px solid #ccc; padding: 4px;">61,38</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_6000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9781</td><td style="border: 1px solid #ccc; padding: 4px;">66,75</td><td style="border: 1px solid #ccc; padding: 4px;">238,59</td><td style="border: 1px solid #ccc; padding: 4px;">0,52 %</td><td style="border: 1px solid #ccc; padding: 4px;">4,91</td><td style="border: 1px solid #ccc; padding: 4px;">4,89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_6000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,973</td><td style="border: 1px solid #ccc; padding: 4px;">327,44</td><td style="border: 1px solid #ccc; padding: 4px;">48,81</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_7000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9804</td><td style="border: 1px solid #ccc; padding: 4px;">74,64</td><td style="border: 1px solid #ccc; padding: 4px;">213,49</td><td style="border: 1px solid #ccc; padding: 4px;">0,38 %</td><td style="border: 1px solid #ccc; padding: 4px;">5,28</td><td style="border: 1px solid #ccc; padding: 4px;">5,27</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_7000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9767</td><td style="border: 1px solid #ccc; padding: 4px;">394,24</td><td style="border: 1px solid #ccc; padding: 4px;">40,53</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_8000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9823</td><td style="border: 1px solid #ccc; padding: 4px;">82,28</td><td style="border: 1px solid #ccc; padding: 4px;">193,59</td><td style="border: 1px solid #ccc; padding: 4px;">0,27 %</td><td style="border: 1px solid #ccc; padding: 4px;">6,86</td><td style="border: 1px solid #ccc; padding: 4px;">6,83</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_8000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9797</td><td style="border: 1px solid #ccc; padding: 4px;">564,14</td><td style="border: 1px solid #ccc; padding: 4px;">28,33</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_9000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9837</td><td style="border: 1px solid #ccc; padding: 4px;">90,08</td><td style="border: 1px solid #ccc; padding: 4px;">176,96</td><td style="border: 1px solid #ccc; padding: 4px;">0,16 %</td><td style="border: 1px solid #ccc; padding: 4px;">7,63</td><td style="border: 1px solid #ccc; padding: 4px;">7,61</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_9000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9821</td><td style="border: 1px solid #ccc; padding: 4px;">687,25</td><td style="border: 1px solid #ccc; padding: 4px;">23,25</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_10000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9848</td><td style="border: 1px solid #ccc; padding: 4px;">97,64</td><td style="border: 1px solid #ccc; padding: 4px;">163,31</td><td style="border: 1px solid #ccc; padding: 4px;">0,08 %</td><td style="border: 1px solid #ccc; padding: 4px;">8,38</td><td style="border: 1px solid #ccc; padding: 4px;">8,36</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_10000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,984</td><td style="border: 1px solid #ccc; padding: 4px;">818,64</td><td style="border: 1px solid #ccc; padding: 4px;">19,53</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
              </tbody>
            </table>
          <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><pre><code>GET <INDEX_NAME>/_search
{
  "query": {
    "knn": {
      "<DENSE_VECTOR_FIELD_NAME>": {
        "vector": [...],
        "k": <NUMBER_OF_CANDIDATES>,
        "method_parameters": {
          "ef_search": <NUMBER_OF_CANDIDATES>
        },
        "rescore": {
          "oversample_factor": <OVERSAMPLE>
        },
        "filter": {
          <SOME_FILTER>
        }
      }
    }
  },
  "size": <RESULT_SIZE>,
  "_source": {
    "excludes": [
      "<DENSE_VECTOR_FIELD_NAME>"
    ]
  }
}</code></pre><ul><li><code>&quot;size&quot;: &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.</li><li><code>&quot;k&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: Le nombre de candidats voisins les plus proches.</li><li><code>&quot;ef_search&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: Lle nombre de vecteurs à examiner.</li><li><code>&quot;oversample_factor&quot;: &lt;OVERSAMPLE&gt;</code>: Combien de vecteurs candidats sont récupérés avant réévaluation.</li></ul><p>Elasticsearch</p><pre><code>GET <INDEX_NAME>/_search
{
  "query": {
    "knn": {
      "field": "<DENSE_VECTOR_FIELD_NAME>",
      "query_vector": [...],
      "k": <NUMBER_OF_CANDIDATES>,
      "num_candidates": <NUMBER_OF_CANDIDATES>,
      "rescore_vector": {
        "oversample": <OVERSAMPLE>
      },
      "filter": {
        <SOME_FILTER>
      }
    }
  },
  "size": <RESULT_SIZE>,
  "_source": {
    "excludes": [
      "<DENSE_VECTOR_FIELD_NAME>"
    ]
  }
}</code></pre><ul><li><code>&quot;size&quot;: &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.</li><li><code>&quot;k&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: Nombre de voisins les plus proches à renvoyer de chaque partition.</li><li><code>&quot;num_candidates&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: Nombre de candidats au plus proche voisin à considérer par partition lors d&#x27;une opération de recherche <code>knn</code>.</li><li><code>&quot;oversample&quot;: &lt;OVERSAMPLE&gt;</code>: Combien de vecteurs candidats sont récupérés avant réévaluation.</li></ul><p>Exemple</p><p><code>Knn</code> la requête, (<code>100_500_1</code>), serait la suivante :</p><p>OpenSearch</p><pre><code>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"
    ]
  }
}</code></pre><p>Elasticsearch</p><pre><code>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"
    ]
  }
}</code></pre><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f81002c9186e4c2d3e92f49d72418fee9860fc5e-761x401.png" style="width: 100%;" /><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>Un vecteur dense à 128 dimensions utilisé pour la recherche approximative par kNN.</li><li>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.</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><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">Qu&#x27;est-ce que l&#x27;ingénierie contextuelle ?</a></li><li><a href="https://www.elastic.co/search-labs/blog/series/context-engineering-hybrid-search-evolution">L&#x27;évolution du rechercher hybride et de l&#x27;ingénierie contextuelle</a></li><li><a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">L&#x27;impact de la pertinence dans l&#x27;ingénierie du contexte pour les agents IA</a></li></ol>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[jina-embeddings-v5-text : modèles d’embeddings textuels compacts et de pointe pour la recherche et les applications intelligentes]]></title>
            <description><![CDATA[Présentation des modèles jina-embeddings-v5-text, dont jina-embeddings-v5-text-small et jina-embeddings-v5-text-nano, ainsi que de leur utilisation en tant que modèles d’embeddings multilingues via Elastic Inference Service (EIS).]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/jina-embeddings-v5-text</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/jina-embeddings-v5-text</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Scott Martens, Sofia Vasileva]]></dc:creator>
            <pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e98dd30ab925b4bb32830228d71a1a51d02a0917-1600x840.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Jina AI et Elastic lancent <code>jina-embeddings-v5-text</code>, une famille de modèles d’embeddings textuels compacts et hautes performances, offrant des performances de pointe pour des modèles de taille comparable sur l’ensemble des principaux types de tâches.</p><p>La famille comprend deux modèles :</p><ul><li><code>jina-embeddings-v5-text-small</code></li><li><code>jina-embeddings-v5-text-nano</code></li></ul><p>Ces modèles sont le fruit d’une nouvelle méthode d’entraînement innovante pour les modèles d’embeddings. Tous deux surpassent des modèles bien plus volumineux, tout en réduisant les besoins en mémoire et en ressources de calcul, et en accélérant les temps de réponse.</p><p>Le modèle <code>jina-embeddings-v5-text-small</code> compte 677 millions de paramètres, prend en charge une fenêtre de contexte d’entrée de 32 768 tokens et génère par défaut des embeddings de 1 024 dimensions.</p><p><code>jina-embeddings-v5-text-nano</code> pèse environ un tiers de la taille de son homologue, avec 239 millions de paramètres et une fenêtre de contexte d’entrée de 8 192 tokens, offrant des embeddings de 768 dimensions.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Nom du modèle</td><td style="border: 1px solid #ccc; padding: 4px;">Taille totale</td><td style="border: 1px solid #ccc; padding: 4px;">Taille de la fenêtre contextuelle d'entrée</td><td style="border: 1px solid #ccc; padding: 4px;">Taille des embeddings</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">jina-v5-text-small</td><td style="border: 1px solid #ccc; padding: 4px;">677M paramètres</td><td style="border: 1px solid #ccc; padding: 4px;">32 768 tokens</td><td style="border: 1px solid #ccc; padding: 4px;">1 024 dimensions</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">jina-v5-text-nano</td><td style="border: 1px solid #ccc; padding: 4px;">239M paramètres</td><td style="border: 1px solid #ccc; padding: 4px;">8 192 tokens</td><td style="border: 1px solid #ccc; padding: 4px;">768 dimensions</td>
                    </tr>
              </tbody>
            </table>
          <p>Ces deux modèles se classent parmi les meilleurs pour les performances globales au benchmark MTEB (<a href="https://huggingface.co/spaces/mteb/leaderboard">Massive Text Embedding Benchmark</a>) et au benchmark multilingue MTEB. Parmi les modèles ayant moins de 500 millions de paramètres, <code>jina-embeddings-v5-text-nano</code> est le plus performant, malgré moins de 250 millions de paramètres, et <code>jina-embeddings-v5-text-small</code> est le leader parmi les modèles d’embeddings multilingues de moins de 750 millions de paramètres.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d97126285fa196c3045b1913b7754e4b186c1e4c-1300x1100.png" style="width: 100%;" /><p>Ces modèles sont disponibles via Elastic Inference Service (EIS), via une API en ligne, et peuvent également être déployés en local. Pour savoir comment accéder aux modèles <code>jina-embeddings-v5-text</code>, consultez la section « <strong>Commencer</strong> » ci-dessous.</p><p>Les modèles d’embeddings et l’indexation sémantique améliorent considérablement la précision des algorithmes de recherche, tout en répondant à de nombreux autres cas d’usage liés à la similarité sémantique et à l’extraction de sens, par exemple :</p><ul><li>Détection de textes en double.</li><li>Reconnaissance des paraphrases et des traductions.</li><li>Découverte de thématiques.</li><li>Moteurs de recommandation.</li><li>Analyse des sentiments et des intentions.</li><li>Filtrage des spams.</li><li>Et bien d&#x27;autres encore.</li></ul><h2><strong>Fonctionnalités</strong></h2><p>Cette nouvelle famille de modèles propose un ensemble de fonctionnalités conçues pour améliorer la pertinence et réduire les coûts.</p><h3>Optimisation des tâches</h3><p>Nous avons optimisé les modèles <code>jina-embeddings-v5-text</code> pour quatre grands types de tâches :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Tâche</td><td style="border: 1px solid #ccc; padding: 4px;">Exemples de cas d'utilisation</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Récupération</td><td style="border: 1px solid #ccc; padding: 4px;">Recherche à l’aide de requêtes en langage naturel et récupération des correspondances les plus pertinentes au sein d’une collection de documents.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Correspondance de texte</td><td style="border: 1px solid #ccc; padding: 4px;">Similarité sémantique, déduplication, alignement des paraphrases et des traductions, et plus encore.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Clustering</td><td style="border: 1px solid #ccc; padding: 4px;">Découverte de thématiques, organisation automatique de collections de documents.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Classification</td><td style="border: 1px solid #ccc; padding: 4px;">Catégorisation de documents, analyse des sentiments et détection des intentions, tâches similaires.</td>
                    </tr>
              </tbody>
            </table>
          <p>Optimiser un modèle pour une tâche implique généralement de faire des compromis sur une autre. La plupart des modèles d’embeddings n’offrent donc des performances compétitives que pour un seul type de tâche. En revanche, les modèles <code>jina-embeddings-v5-text</code> peuvent se spécialiser dans les quatre catégories sans compromettre l’entraînement, grâce à des <a href="https://arxiv.org/abs/2106.09685">adaptateurs Low-Rank Adaptation (LoRA)</a> spécifiques à chaque tâche.</p><p>Les adaptateurs LoRA sont une sorte de plugin pour un modèle d’IA, qui en modifie fortement le comportement tout en n’augmentant que légèrement sa taille totale. Au lieu d’avoir un modèle distinct pour chaque tâche, chacun comportant des centaines de millions de paramètres, la famille de modèles <code>jina-embeddings-v5-text</code> vous permet d’utiliser un seul modèle, associé à un adaptateur LoRA compact pour chaque tâche. Cela permet d’économiser de la mémoire, de l’espace de stockage et de réduire les coûts d’inférence.</p><h3>Troncature des embeddings</h3><p>Nous avons entraîné les modèles <code>jina-embeddings-v5-text</code> avec <a href="https://arxiv.org/abs/2205.13147">Matryoshka Representation Learning (MRL)</a>, ce qui vous permet de réduire la taille de vos embeddings tout en limitant l’impact sur leur qualité.</p><p>Par défaut, <code>jina-embeddings-v5-text-small</code> génère des vecteurs d’embeddings de 1 024 dimensions, chaque valeur étant représentée sur 16 bits, ce qui porte la taille de chaque embedding à 2 Ko. Pour une vaste collection de documents, cela peut représenter un volume de données important à stocker. La recherche dans une base de données vectorielle remplie d’embeddings est proportionnelle à la taille de la base et au nombre de dimensions de chaque vecteur stocké.</p><p>Vous pouvez toutefois réduire de moitié la taille des embeddings (en supprimant 512 des 1 024 dimensions), diminuer l’espace occupé de moitié et doubler la vitesse de recherche. Cela a un impact sur les performances. Supprimer des informations réduit la précision. Mais comme le montre le graphique ci-dessous, supprimer la moitié de l’embedding n’entraîne qu’une légère baisse des performances :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f11969682c81bcf22b5b29a6204ba6ec40471b3d-900x700.png" style="width: 100%;" /><p>Tant que vos embeddings comportent au moins 256 dimensions, la perte de précision devrait rester relativement faible. En dessous de ce seuil, en revanche, la pertinence et la précision se dégradent rapidement.</p><p>La troncature des embeddings de cette manière vous permet de gérer vos arbitrages entre précision et coûts de calcul. Vous disposez ainsi des leviers nécessaires pour obtenir des gains d’efficacité significatifs et réduire sensiblement les coûts de votre Search AI.</p><h3>Quantification robuste</h3><p>La <em>quantification </em>constitue une autre méthode pour réduire la taille des embeddings. Au lieu de supprimer une partie de chaque embedding, la quantification diminue la précision des valeurs numériques qui le composent. Les modèles <code>jina-embeddings-v5-text</code> génèrent des embeddings avec des valeurs sur 16 bits, mais nous pouvons arrondir ces valeurs, ce qui réduit leur précision ainsi que le nombre de bits nécessaires pour les stocker. Dans le cas le plus extrême, il est possible de ramener chaque valeur à un seul bit (0 ou 1), ce qui compresse les embeddings par défaut de 1 024 dimensions de <code>jina-embeddings-v5-text</code>de 2 kilooctets à 128 octets, soit une réduction de 94 % grâce à la seule quantification binaire. Comme pour la troncature, cela permet de réaliser d’importantes économies de mémoire et de ressources de calcul. Cependant, à l’instar de la troncature, la quantification réduit la précision des embeddings.</p><p>Nous avons entraîné les modèles <code>jina-embeddings-v5-text</code> pour fonctionner avec la <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/bbq">Better Binary Quantization (BBQ)</a> d’Elasticsearch, en minimisant la perte de précision. Les tests comparatifs des embeddings binarisés issus de ces modèles montrent des performances presque équivalentes à celles de leurs versions non binarisées. Consultez <a href="https://arxiv.org/abs/2602.15547">le rapport technique</a> pour accéder à des études d’ablation détaillées sur les performances de la binarisation.</p><h3>Performance multilingue</h3><p>De nombreux modèles d’embeddings sont multilingues, car ils ont été entraînés sur des corpus couvrant un grand nombre de langues. Cela ne signifie pas pour autant qu’ils offrent des performances équivalentes dans toutes les langues prises en charge.</p><p>Nous avons identifié 211 langues dans le benchmark multilingue MTEB et les avons isolées afin de comparer nos modèles à des modèles similaires, langue par langue. L’image ci-dessous synthétise nos résultats sous la forme d’une carte thermique. Chaque zone correspond à une langue (identifiée par son code ISO-639) et plus la couleur est verte, meilleures sont les performances du modèle par rapport à la moyenne des modèles similaires :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/852ac5d0f1977bb0c1124d87f8863a9bb94eb7da-1600x765.png" style="width: 100%;" /><p>Bien que la précision varie selon les langues, les modèles <code>jina-embeddings-v5-text</code> atteignent des performances de pointe, ou proches de l’état de l’art, dans la majorité des langues du monde.</p><p>Pour en savoir plus sur les performances multilingues, consultez le <a href="https://arxiv.org/abs/2602.15547">rapport technique<code>jina-embeddings-v5-text</code></a> .</p><h2><strong>Jina in Elastic : une IA native de pointe pour la recherche</strong></h2><p>Avec les modèles <code>jina-embeddings-v5-text</code> sur EIS, vous exécutez des modèles d’embeddings multilingues hautes performances de manière native dans Elasticsearch, avec une inférence entièrement gérée, accélérée par GPU, et sans infrastructure à provisionner ni à faire évoluer. Les modèles <code>jina-embeddings-v5-text</code> enrichissent le catalogue de modèles EIS en proposant des modèles multilingues compacts, tirant parti des dernières avancées en matière d’IA. Ces modèles affichent des performances de pointe sur les benchmarks de recherche d’information et d’analyse de données standard, tout en offrant une prise en charge multilingue inégalée à l’échelle mondiale.</p><p>Avec deux modèles de tailles très différentes, vous pouvez déterminer celui qui convient le mieux à vos applications et à votre budget. De plus, grâce à des embeddings robustes qui restent performants lorsqu’ils sont tronqués à des tailles plus réduites ou quantifiés avec une précision moindre, les modèles <code>jina-embeddings-v5-text </code>offrent des opportunités supplémentaires d’économies concrètes en matière de stockage, de coûts de calcul et de latence de traitement.</p><p>Avec la famille <code>jina-embeddings-v5-text</code>, Jina Reranker et la recherche vectorielle rapide et BM25 d’Elastic, vous bénéficiez désormais d’une <a href="https://www.elastic.co/docs/solutions/search/hybrid-search">recherche hybride</a> de bout en bout, de pointe, proposée par Elastic. Lorsque vous avez besoin des résultats les plus pertinents, que ce soit pour des pipelines de génération augmentée par récupération (RAG), des applications de recherche ou des analyses de données, Elastic associé aux modèles Search AI de Jina vous garantit une qualité robuste et un excellent rapport coût-efficacité.</p><h2><strong>Premiers pas</strong></h2><p>Les modèles <code>jina-embeddings-v5-text</code> sont entièrement intégrés dans <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a>, et vous pouvez les utiliser en définissant le<strong> </strong>champ <strong><code>type</code></strong>sur<strong> <code>semantic_text</code></strong> lors de la création de votre index et en spécifiant le modèle (<code>jina-embeddings-v5-text-small</code> ou <code>jina-embeddings-v5-text-nano</code>) dans le<strong> </strong>champ <code>inference_id</code>, comme dans cet exemple :</p><pre><code>PUT multilingual-semantic-index
{
  "mappings": {
    "properties": {
      "content": {
        "type": "semantic_text",
        "inference_id": ".jina-embeddings-v5-text-small"
      }
    }
  }
}

# Ingest data about France
POST multilingual-semantic-index/_doc
{
  "content": "The capital of France is Paris"}

GET multilingual-semantic-index/_search
{
  "query": {
    "semantic": {
      "field": "content",
      "query": "What is the French capital?"
    }
  }
}</code></pre><p>Elasticsearch sélectionne automatiquement l’adaptateur LoRA approprié lors de l’indexation et de la recherche. Les dimensions de l&#x27;intégration (voir la section « <strong>Troncature des intégrations »</strong> ci-dessus) peuvent être définies lors de la <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put">création d&#x27;un point de terminaison d&#x27;inférence personnalisé</a>.</p><p>Consultez la <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector">documentation Elasticsearch</a> pour plus d&#x27;informations sur l&#x27;utilisation des modèles <strong><code>jina-embeddings-v5-text</code> </strong> .</p><h2><strong>Plus d&#x27;informations</strong></h2><p>Pour en savoir plus sur les modèles <code>jina-embeddings-v5-text</code>, lisez les <a href="https://jina.ai/news/jina-embeddings-v5-text-distilling-4b-quality-into-sub-1b-multilingual-embeddings/">notes de publication sur le blog de Jina AI</a> et le <a href="https://arxiv.org/abs/2602.15547">rapport technique</a>, qui contiennent des informations techniques détaillées sur les performances et la nouvelle procédure de formation innovante de Jina AI. Pour plus d&#x27;informations sur le téléchargement et l&#x27;exécution de ces modèles localement, consultez la <a href="https://huggingface.co/collections/jinaai/jina-embeddings-v5-text"><code>jina-embeddings-v5-text</code> page de la collection</a> sur Hugging Face.</p><p>Les modèles Jina AI sont disponibles sous <a href="https://spdx.org/licenses/CC-BY-NC-4.0">licence CC-BY-NC-4.0</a>, vous êtes donc libre de les télécharger et de les essayer, mais pour un usage commercial, veuillez contacter <a href="https://www.elastic.co/contact?pg=global&amp;plcmt=nav&amp;cta=205352">les ventes d’Elastic</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Garantir une précision sémantique avec un score minimum]]></title>
            <description><![CDATA[Améliorez la précision sémantique en utilisant des seuils de score minimum. Cet article présente des exemples concrets de recherche sémantique et hybride. ]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/semantic-precision-minimum-score</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/semantic-precision-minimum-score</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Mattias Brunnert]]></dc:creator>
            <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8b3b5910abfe16d48d309341a0027008b16c4340-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La recherche sémantique a ouvert un monde d&#x27;opportunités pour améliorer la pertinence des recherches. Les modèles clairsemés et denses de haute qualité, tels qu&#x27;ELSER, E5 et Jina Embedding v4, renvoient des résultats pertinents en fonction du sens des mots, plutôt que de la correspondance de mots-clés. Cependant, la recherche sémantique renvoie parfois des résultats non pertinents en fin de liste ou pour des requêtes dont l&#x27;index ne contient aucun résultat pertinent. Cette caractéristique des modèles clairsemés et denses peut induire les utilisateurs en erreur ou gaspiller des jetons précieux pour les grands modèles de langage (LLM).</p><p>Dans cet article, vous apprendrez comment utiliser le paramètre de score minimum pour augmenter la précision de vos résultats de recherche sémantique. Si vous souhaitez tester les exemples fournis dans cet article de blog, accédez au <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/ensuring-semantic-precision-with-minimum-score/ensuring_semantic_precision_with_minimum_score.ipynb">notebook Jupyter associé</a>.</p><h2>Contexte : précision et rappel</h2><p>En matière de pertinence de recherche, la <em>précision </em>et le <em>rappel </em>sont des concepts clés. Nous encourageons vivement les lecteurs qui ne les connaissent pas encore à se familiariser avec ces concepts. Voici un résumé.</p><ul><li><strong>Précision</strong> : la fraction des résultats de recherche renvoyés qui sont pertinents pour l&#x27;utilisateur.</li><li><strong>Rappel</strong> : la fraction de tous les documents pertinents du corpus inclus dans l&#x27;ensemble des résultats de recherche.</li></ul><p>En d&#x27;autres termes, la précision consiste à <strong>ne renvoyer </strong>que les résultats pertinents, tandis que le rappel consiste à <strong>renvoyer tous </strong>les résultats pertinents. Comme vous pouvez l&#x27;imaginer, ces deux exigences sont souvent contradictoires. La recherche sémantique a généralement un rappel très élevé, mais peut être à la peine en termes de précision. Poursuivez votre lecture pour découvrir comment contourner ce problème.</p><h2>Présentation du paramètre de score minimum</h2><p>Le paramètre &quot;min_score&quot; nous permet d&#x27;améliorer la précision en fixant un score minimum, ce qui tronquera le résultat en supprimant toutes les correspondances dont le score est inférieur au seuil défini. Voici un exemple simple :</p><pre><code>GET search-movies/_search
{
  "retriever": {
    "linear": {
      "min_score": 4,
      "retrievers": [
        ...
      ]
    }
  }
}</code></pre><h2>Normalisation du score</h2><p>Définir un score minimum est une bonne chose ; cependant, tous les modèles sémantiques ne renvoient pas un score adapté à un seuil statique. ELSER, par exemple, renvoie un score qui n&#x27;est pas limité. <a href="https://huggingface.co/intfloat/e5-small#faq">Certains</a> scores de modèles denses sont fortement regroupés et n&#x27;ont de sens que dans le contexte de la requête spécifique.</p><p>Pour la plupart des cas de recherche sémantique, nous recommandons d&#x27;utiliser une approche de normalisation avant d&#x27;appliquer le &quot;min_score&quot;. La normalisation garantit que le score du document se situe dans un intervalle défini. Les extracteurs Elasticsearch proposent deux <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever#linear-retriever-normalizers">normalisateurs</a> de ce type, &quot;l2_norm&quot; et &quot;minmax&quot;. Le plus couramment utilisé est &quot;minmax&quot;, car il est facile à comprendre et fonctionne bien dans de nombreux scénarios. Voici les principales propriétés de &quot;minmax&quot; :</p><ul><li>Les scores des documents sont distribués entre 0 et 1.</li><li>Le document ayant le score le plus élevé est toujours noté 1.</li><li>Le document ayant obtenu le score le plus bas est toujours noté 0.<ul><li>Cela peut le rendre moins adapté à la recherche par mots-clés. Voir la section &quot;Recherche hybride&quot; pour plus de détails.</li></ul></li></ul><p>Voici un exemple de requête sémantique normalisée avec <code>min_score</code>. La taille de la fenêtre de classement a été augmentée à 500 pour nous permettre de renvoyer une liste plus longue de résultats de recherche, en commençant à 100.</p><pre><code>GET search-movies/_search
{
  "size": 100,
  "_source": [
    "title", "overview"
  ],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        }
      ]
    }
  }
}</code></pre><p>La taille a été définie sur une valeur plus élevée que celle habituellement observée en production. Cela nous permet de contrôler la qualité des résultats de recherche et de les optimiser.</p><h2>Recherche hybride utilisant l&#x27;extracteur linéaire</h2><p>Pour la recherche hybride, l&#x27;approche la plus simple consiste à normaliser tous les scores, à leur attribuer des pondérations et à appliquer un score minimal. Notez qu&#x27;en choisissant des pondérations dont la somme est égale à 1, le score total reste compris entre 0 et 1. Cela facilite l&#x27;interprétation des scores finaux et l&#x27;ajustement de <code>min_score</code>. Voici un exemple :</p><pre><code>GET search-movies/_search
{
  "size": 100,
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "weight": 0.6,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        },
        {
          "weight": 0.4,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "multi_match": {
                  "query": "superhero movie",
                  "fields": ["overview","keywords", "title"],
                  "type": "cross_fields",
                  "minimum_should_match": "2"
                }
              }
            }
          }
        }
      ]
    }
  }
}</code></pre><h2>Recherche hybride à l&#x27;aide de la RRF</h2><p>Avec le BM25, nous contrôlons souvent la précision par d&#x27;autres moyens, par exemple en utilisant l&#x27;opérateur <code>AND</code> ou <code>minimum_should_match</code>. De plus, les requêtes composées de termes uniques, précis et rares entraîneront naturellement des résultats de recherche peu nombreux, souvent tous très pertinents. Cela peut causer les problèmes suivants :</p><ul><li>Les résultats situés plus loin dans le classement reçoivent un score normalisé faible dans l&#x27;extracteur BM25, même si le score BM25 absolu est proche des meilleurs résultats.</li><li>Si l&#x27;on ajoute un score BM25 très faible au score sémantique, le total peut être considéré comme le score sémantique.</li><li>L&#x27;absence de contribution au score BM25 peut entraîner la suppression du document par le <code>min_score threshold</code>.</li></ul><p>Comme solution, nous pouvons plutôt utiliser la fusion des rangs réciproques (RRF) pour combiner les résultats BM25 et sémantiques. RRF contourne la difficulté de comparer les scores de différents algorithmes de recherche en se concentrant plutôt sur la position dans chaque ensemble de résultats. Dans ce scénario, le <code>min_score</code> est uniquement appliqué à l&#x27;extracteur sémantique.</p><pre><code>GET search-movies/_search
{
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "rrf": {
      "rank_window_size": 500,
      "retrievers": [
        {
          "linear": {
            "rank_window_size": 500,
            "min_score": 0.25,
            "retrievers": [
              {
                "normalizer": "minmax",
                "retriever": {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "overview_vector",
                        "query": "superhero movie"
                      }
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "standard": {
            "query": {
              "multi_match": {
                "query": "superhero movie",
                "fields": ["overview", "keywords","title"],
                "type": "cross_fields",
                "minimum_should_match": "2"
              }
            }
          }
        }
      ]
    }
  }
}</code></pre><h2>Conclusion</h2><p>En utilisant <code>min_score</code>, nous avons montré comment réduire le nombre de faux positifs dans nos ensembles de résultats causés par le fort rappel des algorithmes de recherche sémantique. Pour en savoir plus sur les extracteurs, veuillez consulter cet <a href="https://www.elastic.co/search-labs/blog/elasticsearch-retrievers">article de blog</a> et la <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">documentation d&#x27;Elasticsearch</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Gestion des dépendances sur Kubernetes]]></title>
            <description><![CDATA[Comment simplifier la gestion des dépendances sur Kubernetes en utilisant Renovate CLI et Argo Workflows.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/dependency-management-kubernetes</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/dependency-management-kubernetes</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Nikos Fotiou]]></dc:creator>
            <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/00519840e6eec7101c1fb096afcae976ee0c454e-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Voici comment nous avons construit une plateforme de gestion des dépendances auto-hébergée en utilisant Kubernetes, Argo Workflows, Argo Events et Renovate CLI pour automatiser les mises à jour, traiter rapidement les vulnérabilités et expositions courantes (CVE) et transmettre efficacement les nouvelles versions de packages à des milliers de référentiels.</p><h2><strong>Gestion des dépendances chez Elastic</strong></h2><p>Chez Elastic, nous devons gérer des centaines, voire des milliers de référentiels privés et publics. Lorsqu&#x27;une CVE critique est découverte, nous avons besoin de réponses et d&#x27;actions immédiates : quels référentiels sont vulnérables ? Dans quel délai pouvons-nous les réparer ? Outre la sécurité, des questions de productivité se posent également : comment transmettre rapidement la publication d&#x27;une nouvelle version d&#x27;un package à tous les référentiels qui en dépendent, sans passer trop de temps à effectuer des tâches manuelles ?</p><p>La recherche de méthodes de gestion des dépendances a été motivée à l&#x27;origine par la nécessité d&#x27;établir une base sécurisée avec des mises à jour automatisées pour <a href="https://www.elastic.co/blog/reducing-cves-in-elastic-container-images">réduire les CVE</a>. Après avoir soigneusement réfléchi aux solutions de gestion des dépendances, nous avons d&#x27;abord commencé à travailler sur une infrastructure auto-hébergée. Nous utilisions notre propre cluster Kubernetes pour exécuter Mend Renovate Community auto-hébergé. L&#x27;idée était de pouvoir fournir une plateforme de gestion des dépendances à laquelle nos utilisateurs pourraient accéder en libre-service.</p><p>L&#x27;expérience initiale s&#x27;est avérée fructueuse, si bien que de plus en plus d&#x27;équipes ont commencé à adopter notre plateforme et à l&#x27;utiliser dans le cycle de vie quotidien de leurs référentiels pour les mises à jour et les correctifs CVE. Cela s&#x27;est passé si vite que nous avons rapidement atteint les limites de notre installation auto-hébergée.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e14d9f98d4eccaa08a335d5bd23d88e5debbb344-1600x1103.png" style="width: 100%;" /><h3><strong>Le défi : comment pouvons-nous scaler une plateforme de gestion des dépendances dans une grande organisation disposant d&#x27;un nombre important de référentiels ?</strong></h3><p>Notre plateforme de gestion des dépendances traitait un référentiel à la fois et le modèle de traitement séquentiel ne pouvait pas suivre le rythme, compte tenu du grand nombre de référentiels que nous gérons. Nous avions déjà constaté que le problème provenait du fait <strong>qu&#x27;une seule instance</strong> de notre outil de gestion des dépendances pouvait traiter notre liste importante et toujours croissante de référentiels. Les référentiels attendaient parfois pendant plusieurs heures dans une file d&#x27;attente. Plus de 50 % de nos référentiels n&#x27;étaient même pas traités quotidiennement. Autrement dit, plus de la moitié de nos référentiels attendaient plus de 24 heures entre les analyses.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/45ade5bda08f82bed0b3d0d3736cbd6f056e7a4e-1312x816.jpg" style="width: 100%;" /><p>Les grands référentiels ont créé des goulots d&#x27;étranglement plus importants, en raison de leurs bases de code volumineuses et de leurs multiples requêtes pull ouvertes. Les événements du webhook GitHub ont perturbé la séquence. La fusion automatique est devenue peu fiable car le moment des analyses était imprévisible. Nous avions fait une promesse à nos utilisateurs concernant la fréquence des analyses, mais nous n&#x27;avons pas pu la tenir.</p><h3><strong>La décision de développer en interne : répondre aux besoins uniques de scalabilité et de sécurité d&#x27;Elastic</strong></h3><p>Bien que nous envisagions des options commerciales, dont l&#x27;<strong>édition auto-hébergée Renovate Enterprise de Mend</strong>, nous avions en interne chez Elastic quelques initiatives clés en cours de développement.</p><p>Notre décision de créer une plateforme en interne a été motivée par la prise de conscience que seule une solution hautement personnalisée pouvait répondre aux exigences spécifiques et non négociables d&#x27;Elastic :</p><ol><li><strong>Investissement dans notre plateforme de développement interne</strong> : à l&#x27;époque, nous avions déjà investi massivement dans notre plateforme de développement interne. Nous réfléchissions à la manière d&#x27;intégrer chacun de nos services à cette plateforme et nous concevions des solutions pour y parvenir. Cela impliquait de tester nos propres règles et pratiques pour notre plateforme de gestion des dépendances. De plus, de nouvelles directives entraient en vigueur et nous souhaitions concevoir la plateforme en amont.</li><li><strong>Intégration native et personnalisation du workflow</strong> : nous avions besoin d&#x27;une intégration simple à nos outils et processus internes. Par exemple, nous souhaitions centraliser la configuration sous forme de code avec notre catalogue de services (Backstage). L&#x27;utilisation de Backstage nous impose des exigences spécifiques avec lesquelles nous voulions que notre plateforme soit compatible. Ainsi, bien qu&#x27;il soit possible d&#x27;utiliser les API auto-hébergées de Renovate en complément de notre automatisation Backstage, cela ne couvrirait pas entièrement nos processus internes.</li><li><strong>Sécurité renforcée par défense en profondeur spécifique à Elastic</strong> : notre conformité stricte en matière de sécurité exigeait des mécanismes de sécurité sur mesure, adaptés à notre écosystème. Nous nous efforcions de <a href="https://entro.security/blog/how-elastic-scaled-secrets-nhi-security-elastics-playbook-from-visibility-to-automation/">renforcer la sécurité de notre utilisation des &quot;identités non humaines&quot;.</a> Ce renforcement des accès impliquait que les méthodes d&#x27;authentification non standard auprès de GitHub ne fonctionneraient pas avec un outil standard qui ne prenait pas en charge cette implémentation interne. Notre workflow comprenait la mise en œuvre d&#x27;un modèle de chiffrement des secrets de workflow parent-enfant et l&#x27;utilisation de jetons GitHub temporaires à usage unique. Le développement en interne était la seule solution pratique pour intégrer ces couches de sécurité uniques et minimiser la surface d&#x27;attaque dans notre environnement multicloud complexe.</li></ol><h2><strong>La solution : l&#x27;orchestration des workflows pour la gestion des dépendances</strong></h2><p>Notre solution est née de notre volonté de tirer parti de l&#x27;outil de gestion des dépendances que nous utilisions déjà plutôt que de le remplacer et rechercher d&#x27;autres solutions. Cet outil avait démontré son potentiel, et sa flexibilité est essentielle pour répondre aux différents besoins de notre organisation. Nous avons examiné différentes solutions, et ce qui a guidé notre choix, ce sont les besoins importants et parfois spécifiques que nous devons satisfaire. Nous avons donc décidé de créer une plateforme de gestion des dépendances fiable et évolutive, où chaque référentiel est traité individuellement, éliminant ainsi les goulots d&#x27;étranglement et nous préparant à la croissance.</p><p>Nous avons conçu la plateforme en respectant trois principes fondamentaux :</p><h3><strong>1. Traitement parallèle</strong></h3><p>Chaque référentiel est doté de son propre environnement de gestion des dépendances. Plus de files d&#x27;attente. Notre simultanéité d&#x27;exécution n&#x27;est limitée que par le nombre de ressources que nous utilisons. Nous avons également appliqué une programmation distribuée intelligente pour éviter d&#x27;être limité par GitHub.</p><h3><strong>2. Libre-service</strong></h3><p>Nous utilisons notre catalogue de services (Backstage) pour intégrer et gérer automatiquement les nouveaux référentiels. Grâce à notre propre système de définition des ressources, l&#x27;utilisateur final peut choisir la fréquence de traitement des référentiels, le nombre de ressources à allouer à ses planifications, et activer ou désactiver le traitement à tout moment. Nous prévoyons d&#x27;ajouter d&#x27;autres options à mesure que les besoins de nos utilisateurs évoluent et qu&#x27;ils se familiarisent avec la nouvelle installation.</p><h3><strong>3. Réduction de la portée des secrets et de l’isolation des espaces de noms</strong></h3><p>Pour une sécurité accrue, nous fournissons à nos modules de gestion des dépendances des jetons GitHub éphémères qui sont générés au début de chaque workflow. En outre, nous isolons nos charges de travail dans des espaces de noms spécifiques afin de ne leur fournir que les secrets nécessaires. Nous contrôlons les secrets qui peuvent être accessibles par chaque workflow de gestion des dépendances en utilisant le RBAC de Kubernetes. Nous utilisons également le chiffrement pour transmettre le jeton GitHub du workflow parent au workflow enfant.</p><p>Nous avons reconstruit notre plateforme en utilisant Kubernetes et en exploitant sa puissance. Argo Workflows gère la logique de nos processus et Renovate CLI est configuré pour analyser et traiter un référentiel à la fois.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5560ed20e2bd9ecdd574a9c835126d12b24c332f-1600x1157.png" style="width: 100%;" /><p><strong>L&#x27;intérêt de ce modèle</strong> : nous utilisons des projets open source éprouvés d&#x27;une manière originale, en fournissant de nouveaux exemples fonctionnels pour tous ces projets et, en même temps, en amplifiant la vitesse de développement et en consolidant la réduction des CVE pour nos équipes.</p><h2><strong>Architecture de gestion des dépendances : Quatre microservices</strong></h2><p>La plateforme comprend quatre composants conçus sur mesure :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2b3d4046c05bb261e45d40c59f864eb51fb9eaa9-1217x1600.png" style="width: 100%;" /><h3><strong>Opérateur de workflow (Go/Kubebuilder)</strong></h3><p>Un opérateur Kubernetes gérant le cycle de vie du workflow à travers trois définitions de ressources personnalisées (CRD) :</p><ul><li><strong>CRD RepoConfig</strong> : source unique de référence pour la configuration du référentiel.</li></ul><p>Voici comment RepoConfig est défini dans l&#x27;opérateur :</p><pre><code>// RepoConfig is the Schema for the repoconfigs API
type RepoConfig struct {
	metav1.TypeMeta `json:",inline"`

	// metadata is a standard object metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty,omitzero"`

	// spec defines the desired state of RepoConfig
	// +required
	Spec RepoConfigSpec `json:"spec"`

	// status defines the observed state of RepoConfig
	// +optional
	Status RepoConfigStatus `json:"status,omitempty,omitzero"`
}</code></pre><p>Et voici à quoi ressemblerait une instance de RepoConfig :</p><pre><code>apiVersion: workflows.elastic.co/v1
kind: RepoConfig
metadata:
  generation: 3
  name: elastic-test-repo
  namespace: dependency-management-operator
spec:
  owner: group:my-team
  renovate:
    config:
      resourceGroup: SMALL
      runFrequency: 4h
    enabled: true
  repository: elastic/test-repo</code></pre><ul><li><strong>CRD parent</strong> : gère les workflows Cron pour les analyses programmées.</li></ul><p>À l&#x27;intérieur de la boucle de rapprochement du contrôleur parent, nous nous assurons que les paramètres du workflow sont créés et maintenus à jour, voire supprimés si nécessaire.</p><p>Tout d&#x27;abord, le contrôleur parent obtient certains paramètres configurés globalement pour les workflows :</p><pre><code>func (r *ParentReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, parent *workflowsv1.Parent) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Parent", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		RunFrequency:   parent.Spec.RunFrequency,
		ResourceGroups: "parent",
	}</code></pre><p>Il s&#x27;assure que la configuration du mutex est à jour afin d&#x27;empêcher l&#x27;exécution simultanée de workflows similaires :</p><pre><code>	cfMngr := resources.NewConfigMapManager(r.Client, r.Scheme, r.OperatorConfig.ParentNamespace)
	err := cfMngr.CreateOrUpdateSyncMutexConfigmap(ctx, fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), strings.TrimPrefix(parent.Spec.Repository, "elastic/"), r.OperatorConfig.SemaphoreConcurrencyLimit)</code></pre><p>Ensuite, il crée un gestionnaire de workflow qui est la structure qui va créer ou mettre à jour les workflows Cron et les modèles de workflow :</p><pre><code>	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(parent.Spec.Repository, "/", "-"),
		),
		parent.Namespace,
		"parent-workflow",
		false).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithRepoName(parent.Spec.Repository).
		Init(true, true).
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithWfTemplateName(r.OperatorConfig.ParentWorkflowTemplate).
		WithResources(wfSet.GetResourceCategory()).
		WithSchedule(wfSet.GetCronSchedule()).
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddArgument(true, true, "extra_cli_args").
		SetArgument(true, false, "extra_cli_args", "none").
		AddTemplate(resources.NewParentDAGTemplateInstance()).
		AddTemplate(resources.NewWorkflowsTemplateInstance("check-child-workflows", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("security", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("submit-child-workflow", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector))
	wfMngr.OverWriteCommand("submit-child-workflow", r.OperatorConfig.ChildNamespace)
	wfMngr.OverwriteWfTemplateName("parent-wftmpl")
	wfMngr.AddSynchronization(fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), "{{workflow.parameters.repo_name}}")
	err = wfMngr.CreateOrUpdateCronWorkflow(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update cron workflow: %w", err)
	}
	err = wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil</code></pre><ul><li><strong>CRD enfant</strong> : gère les modèles de workflow avec des ressources par référentiel.</li></ul><p>Le contrôleur enfant a une mission de rapprochement similaire à celle du parent, mais ici, il est responsable des modèles de workflow dans l&#x27;espace de noms enfant qui seront déclenchés par les workflows parents.</p><pre><code>func (r *ChildReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, child *workflowsv1.Child) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Child", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		ResourceGroups: child.Spec.ResourceCategory,
	}
	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(child.Spec.Repository, "/", "-"),
		),
		child.Namespace,
		"runner",
		true).
		Init(false, true). // only manage workflow template
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithSuffix("-child-wftmpl").
		WithRepoName(child.Spec.Repository).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithResources(wfSet.GetResourceCategory()). // will override resources of presets if set
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddTemplate(resources.NewWorkflowsTemplateInstance("runner", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddArgument(false, true, "repo_full_name").
		AddArgument(false, true, "repo_name").
		AddArgument(false, true, "encrypted_token").
		AddArgument(false, true, "extra_cli_args")
	wfMngr.OverWriteCommand("runner", r.OperatorConfig.ChildNamespace)
	err := wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil
}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7ac70492a1266ba02cb8afbafc5a486cb38a0edc-1600x1290.png" style="width: 100%;" /><p>Le modèle multicontrôleur offre une séparation claire : le contrôleur RepoConfig gère l&#x27;intégration/dissociation, le contrôleur parent gère la planification, et le contrôleur enfant gère les modèles d&#x27;exécution.</p><h3><strong>Portail d&#x27;événements GitHub (Go)</strong></h3><p>Proxy sécurisé de webhook qui reçoit les webhooks GitHub, vérifie les signatures, filtre par organisation/référentiel, et redirige vers Argo Events. Nous avons conçu 10 capteurs distincts répondant aux interactions du tableau de bord des dépendances, aux événements de requêtes pull (PR) et aux mises à jour des packages.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4828625456cbd6efa8020a20f10d23f294f98a02-1306x1600.png" style="width: 100%;" /><p>Cette passerelle permet l&#x27;intégration aux applications GitHub par :</p><ul><li>Vérification de la sécurité des signatures des webhooks GitHub entrants.</li><li>Transmission des événements valides à l&#x27;EventSource Argo Events avec tous les en-têtes correspondants et l&#x27;authentification.</li><li>Nous configurons également un authSecret sur l&#x27;EventSource et le fournissons comme en-tête Bearer dans les requêtes transférées.</li><li>Fourniture de logging, indicateurs, et logique de tentatives.</li></ul><p>Le webhook effectue diverses validations sur chaque requête d&#x27;événement GitHub.</p><p>Il s&#x27;assure que certains attributs HTTP sont présents :</p><pre><code>// ValidateRequestMethod checks if the request method is POST.
func ValidateRequestMethod(r *http.Request) error {
	if r.Method != http.MethodPost {
		return fmt.Errorf("method not allowed, only POST is accepted")
	}
	return nil
}

// ValidateRequiredHeaders checks for required GitHub headers.
func ValidateRequiredHeaders(r *http.Request) error {
	eventType := r.Header.Get("X-GitHub-Event")
	deliveryID := r.Header.Get("X-GitHub-Delivery")
	signature := r.Header.Get("X-Hub-Signature-256")
	if eventType == "" || deliveryID == "" || signature == "" {
		return fmt.Errorf("missing required GitHub headers")
	}
	return nil
}

// ValidateUserAgent checks that the User-Agent header starts with GitHub-Hookshot/
func ValidateUserAgent(r *http.Request) error {
	userAgent := r.Header.Get("User-Agent")
	if !strings.HasPrefix(userAgent, "GitHub-Hookshot/") {
		return fmt.Errorf("invalid User-Agent")
	}
	return nil
}</code></pre><p>Tout en validant également la signature de chaque requête et son organisation :</p><pre><code>// ValidateSignature verifies the GitHub webhook signature.
func ValidateSignature(r *http.Request, secret string) ([]byte, error) {
	payload, err := GitHub.ValidatePayload(r, []byte(secret))
	if err != nil {
		return nil, fmt.Errorf("invalid GitHub signature: %w", err)
	}
	return payload, nil
}

// ValidateAllowedOwner checks if the organization login is in the allowed organizations list.
func ValidateAllowedOwner(payload []byte, allowedGitHubOrganizations []string) (string, error) {
	var orgLogin string
	var payloadMap map[string]any
	if err := json.Unmarshal(payload, &payloadMap); err == nil {
		if orgObj, ok := payloadMap["organization"].(map[string]any); ok {
			if login, ok := orgObj["login"].(string); ok {
				orgLogin = login
			} else if name, ok := orgObj["name"].(string); ok {
				orgLogin = name
			}
		}
	}
	if !slices.Contains(allowedGitHubOrganizations, orgLogin) {
		return orgLogin, fmt.Errorf("organization login not allowed")
	}
	return orgLogin, nil
}</code></pre><p>Enfin, il redirige vers Argo Events en fonction du type d&#x27;événement :</p><pre><code>	// Map eventType to Argo `EventSource` path
	var endpoint string
	switch eventType {
	case "push":
		endpoint = "/push"
	case "issues":
		endpoint = "/issues"
	case "pull_request":
		endpoint = "/pull-requests"
	default:
		slog.Info("Ignoring unhandled event type", "event_type", eventType, "delivery_id", deliveryID)
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
		return
	}
	forwardURL := h.config.ArgoEventSourceForwardURL + endpoint</code></pre><p>Du côté d&#x27;Argo Events, 10 capteurs surveillent l&#x27;EventBus d&#x27;Argo pour détecter les nouveaux événements.</p><pre><code>apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: {{ .Values.sensors.packageUpdateOnDefaultBranch.name }}
  namespace: {{ .Release.Namespace }}
spec:
  eventBusName: {{ .Values.eventBus.name }}</code></pre><p>Le script applique ensuite la logique de chaque capteur :</p><pre><code>script: |
          local e = event
          if not e or not e.body or not e.body.repository then
            return false
          end

          -- e.g., "refs/heads/main"
          local ref = e.body.ref
          local default_branch = e.body.repository.default_branch
          if not ref or not default_branch then
            return false
          end

          local expected = "refs/heads/" .. default_branch
          if ref ~= expected then
            return false
          end

        {{- if .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}
          patterns = { {{- range $i, $f := .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}{{ if $i }}, {{ end }}"{{ $f }}"{{- end }} }
        {{- end }}

          local function anyMatch(path)
            if type(path) ~= "string" then return false end
            for _, pat in ipairs(patterns) do
              -- match filename at repo root, or anywhere under subdirs
              if path:match(pat) or path:match(".+/" .. pat) then
                return true
              end
            end
            return false
          end

          local function filesContainPackage(paths)
            if type(paths) ~= "table" then return false end
            for _, p in ipairs(paths) do
              if anyMatch(p) then return true end
            end
            return false
          end

          -- Inspect all commits (GitHub includes added/modified/removed lists)
          local commits = e.body.commits
          if type(commits) ~= "table" then
            -- Fallback: some payloads include only head_commit
            commits = {}
            if type(e.body.head_commit) == "table" then
              table.insert(commits, e.body.head_commit)
            end
          end

          for _, c in ipairs(commits) do
            if filesContainPackage(c.added) or filesContainPackage(c.modified) or filesContainPackage(c.removed) then
              return true
            end
          end

          return false</code></pre><h3><strong>Backstage Syncer (Go)</strong></h3><p>Ce composant interroge notre catalogue de services (Backstage) pour obtenir les entités de ressources réelles du référentiel, les transforme en CRD RepoConfig et maintient la plateforme synchronisée avec les modifications de configuration. Celles-ci sont appliquées en trois minutes.</p><pre><code>repoMap := make(map[string]map[string]interface{})
			for i := range entities {
				entity := &entities[i]
				if entity.Spec.Type != "GitHub-repository" {
					continue
				}

				implRaw, err := json.Marshal(entity.Spec.Implementation)
				if err != nil {
					logger.Error("Failed to marshal implementation", "error", err)
					continue
				}

				var implMap map[string]interface{}
				err = json.Unmarshal(implRaw, &implMap)
				if err != nil {
					logger.Error("Failed to unmarshal implementation map", "error", err)
					continue
				}
				var repoName string
				if specMap, ok := implMap["spec"].(map[string]interface{}); ok {
					if repo, ok := specMap["repository"].(string); ok {
						repoName = repo
					}
				}
				if repoName == "" {
					continue
				}

				var workflowsRaw []byte
				if v, ok := implMap["spec"].(map[string]interface{}); ok {
					if r, ok := v["renovate"]; ok {
						workflowsRaw, _ = json.Marshal(r)
					} else {
						workflowsRaw = []byte(`{}`)
					}
				} else {
					workflowsRaw = []byte(`{}`)
				}

				var workflowsWithDefaults schema.WorkflowsMetadata
				err = json.Unmarshal(workflowsRaw, &rworkflowsWithDefaults)
				if err != nil {
					logger.Error("Failed to unmarshal workflows config", "error", err)
					continue
				}

				workflowsMap := map[string]interface{}{
					"enabled":        workflowsWithDefaults.Enabled,
					"require_pr":     workflowsWithDefaults.RequirePr,
					"resource_group": string(workflowsWithDefaults.ResourceGroup),
					"run_frequency":  string(workflowsWithDefaults.RunFrequency),
				}
				repoMap[repoName] = map[string]interface{}{
					"renovate": workflowsMap,
					"owner":    entity.Spec.Owner,
				}
			}
			logger.Info("Fetched GitHub Repository data from Backstage", "repository_count", len(repoMap), "status_code", resp.StatusCode)</code></pre><p>Enfin, il écrit ces données dans des instances RepoConfig.</p><h3><strong>Base de workflows (mixte : JavaScript, Go, Helm)</strong></h3><p>La couche de base contient des charts Helm, des configurations JavaScript, un wrapper Go pour Renovate CLI avec prise en charge du chiffrement et un indexeur APK personnalisé pour les packages Alpine.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/908d19278face3ce1119dbee9146c1264b6e2f30-1600x873.png" style="width: 100%;" /><h2><strong>Configuration en libre-service</strong></h2><p>Les équipes configurent leurs référentiels de manière déclarative via Backstage :</p><pre><code>spec:
  renovate:
    enabled: true
    config:
      resourceGroup: LARGE      # SMALL | MEDIUM | LARGE  
      runFrequency: "0 */4 * * *"  # Every 4 hours</code></pre><p>Les groupes de ressources allouent le processeur et la mémoire en fonction de la taille du référentiel :</p><ul><li><strong>SMALL</strong> : CPU 500 m, mémoire 1 Go.</li><li><strong>MEDIUM</strong> : CPU 1000 m, mémoire 2 Go.</li><li><strong>LARGE</strong> : CPU 2000 m, mémoire 4 Go.</li></ul><p>La configuration est versionnée, auditable et s&#x27;applique automatiquement.</p><h2><strong>Le modèle parent-enfant</strong></h2><p>Le modèle d&#x27;exécution utilise un modèle de workflow parent-enfant :</p><ul><li><strong>Workflow parent</strong> : workflow Cron léger qui s&#x27;exécute comme prévu. Chiffre les secrets, détermine si une analyse doit être exécutée, transmet la configuration à l&#x27;enfant.</li><li><strong>Workflow enfant</strong> : pod éphémère où s&#x27;exécute Renovate CLI. Allocation dynamique des ressources, déchiffrement des secrets de manière isolée, arrêt après exécution.</li></ul><p>Cette séparation offre sécurité (secrets chiffrés au niveau parent), optimisation des ressources (les parents utilisent des ressources minimales) et scalabilité (les enfants s&#x27;exécutent en parallèle).</p><h2><strong>Résultats</strong></h2><h3><strong>Transformation des performances</strong></h3><ul><li><strong>Avant</strong> : un référentiel à la fois, certains référentiels n&#x27;étaient pas traités, parfois même pendant un jour ou plus, moins de 1 000 analyses par jour.</li><li><strong>Après</strong> : plus de 100 analyses simultanées, généralement 8 000 analyses et jusqu&#x27;à 10 000 analyses enregistrées par jour, limitées uniquement par la quantité de ressources que nous sommes prêts à consacrer et par la façon dont nous gérons les limites de débit de GitHub.</li></ul><h3><strong>Rentabilité</strong></h3><p>Cependant, aussi étrange que cela puisse paraître, exécuter 8 000 pods par jour peut permettre d&#x27;obtenir le même résultat à moindre coût qu&#x27;avec un seul pod fonctionnant en continu pour tenter d&#x27;atteindre le même résultat.</p><p>Dans la configuration précédente, nous utilisions une seule instance qui, en conditions optimales, effectuait 500 à 600 analyses. Par ailleurs, comme différents types de référentiels étaient exécutés sur le même pod, nous devions dimensionner celui-ci pour les plus volumineux. Ce dimensionnement était bien supérieur à notre offre actuelle &quot;extra large&quot;, qui utilise 8 cœurs de processeur et 16 Go de mémoire par pod.</p><p>Pour traiter le volume quotidien actuel, le pod unique devrait s&#x27;exécuter pendant 12 jours. Ainsi, en comparant le coût de ce pod unique fonctionnant pendant 12 jours à celui de 8 000 pods de taille &quot;MEDIUM&quot; s&#x27;exécutant chaque jour, notre nouvelle architecture est bien plus efficace pour un même volume d&#x27;analyses.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrique</td><td style="border: 1px solid #ccc; padding: 4px;">Scénario A (workflows)</td><td style="border: 1px solid #ccc; padding: 4px;">Scénario B (pod unique et exécution de longue durée)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Configuration</td><td style="border: 1px solid #ccc; padding: 4px;">8 000 pods (1 vCPU/2 Go)</td><td style="border: 1px solid #ccc; padding: 4px;">1 pod (8 vCPU / 16 Go)*</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Durée</td><td style="border: 1px solid #ccc; padding: 4px;">10 minutes chacun</td><td style="border: 1px solid #ccc; padding: 4px;">12 jours en continu</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Temps de travail total</td><td style="border: 1px solid #ccc; padding: 4px;">1 333 heures de calcul</td><td style="border: 1px solid #ccc; padding: 4px;">288 heures de calcul</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Coût total</td><td style="border: 1px solid #ccc; padding: 4px;">65,83 $</td><td style="border: 1px solid #ccc; padding: 4px;">113,75 $</td>
                    </tr>
              </tbody>
            </table>
          <p>Cependant, prenons en considération le fait que notre valeur par défaut pour nos charges de travail est définie sur &quot;SMALL&quot;, la grande majorité fonctionnant correctement avec une utilisation CPU de 0,5 Go et 1 Go de RAM, et seules quelques-unes nécessitant une configuration moyenne ou grande. Voyons ce qui se passe si 60 % de nos charges de travail s&#x27;exécutent sur &quot;SMALL&quot;, 30 % sur &quot;MEDIUM&quot; et 10 % sur &quot;LARGE&quot;, ce qui est plus proche de la réalité.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrique</td><td style="border: 1px solid #ccc; padding: 4px;">Scénario A (essaim mixte)</td><td style="border: 1px solid #ccc; padding: 4px;">Scénario B (exécution de longue durée)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Stratégie</td><td style="border: 1px solid #ccc; padding: 4px;">8 000 pods (tailles variées)</td><td style="border: 1px solid #ccc; padding: 4px;">1 pod (8 vCPU / 16 Go)*</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Durée</td><td style="border: 1px solid #ccc; padding: 4px;">10 minutes chacun</td><td style="border: 1px solid #ccc; padding: 4px;">12 jours en continu</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Coût total</td><td style="border: 1px solid #ccc; padding: 4px;">52,66 $</td><td style="border: 1px solid #ccc; padding: 4px;">113,75 $</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Économies</td><td style="border: 1px solid #ccc; padding: 4px;">61,09 $ (54 % moins cher)</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
              </tbody>
            </table>
          <p>Nous pouvons constater que, pour le même volume, nous sommes beaucoup plus rentables dans notre configuration actuelle.</p><h3><strong>Sécurité renforcée</strong></h3><ul><li>Jetons GitHub éphémères (minutes d&#x27;exposition contre plusieurs jours).</li><li>Isolation d&#x27;espace de nom avec limites de contrôle d&#x27;accès basé sur les rôles (RBAC).</li><li>Chiffrement des secrets au repos dans les workflows parents.</li><li>Accès direct au coffre-fort supprimé.</li></ul><h3><strong>Performance prévisible</strong></h3><p>Grâce à une fréquence d&#x27;analyse garantie, nous pouvons enfin définir des objectifs de niveau de service (SLO). La fusion automatique fonctionne de manière fiable. Les équipes ont confiance en la plateforme pour tenir ses promesses.</p><h2><strong>Décisions architecturales clés</strong></h2><p>Voici quelques-unes des décisions de conception majeures qui ont façonné l&#x27;apparence de la plateforme.</p><ul><li><strong>Pourquoi des workflows parent-enfant ?</strong></li></ul><p>Nous avons adopté ce modèle pour mettre en œuvre une stratégie de <strong>défense en profondeur.</strong> En limitant les identifiants sensibles (tels que les secrets d&#x27;applications GitHub) à un espace de noms dédié et sécurisé, nous utilisons le <strong>RBAC</strong> pour garantir que les pods d&#x27;exécution éphémères ne puissent pas accéder arbitrairement aux données sensibles. De récentes vulnérabilités de la chaîne d&#x27;approvisionnement (par exemple, les attaques <strong>&quot;Shai Hulud&quot;</strong> ciblant l&#x27;intégration continue et le déploiement continu [CI/CD]) ont démontré l&#x27;importance cruciale d&#x27;isoler les environnements d&#x27;exécution qui exécutent des scripts dynamiques depuis le magasin d&#x27;identifiants.</p><p>Simultanément, ce découplage permet <strong>une optimisation granulaire des ressources</strong>. Les workflows &quot;parents&quot; agissent comme des orchestrateurs légers avec un encombrement minimal, tandis que les workflows &quot;enfants&quot; gèrent l&#x27;analyse des dépendances gourmande en ressources IT. Cette séparation simplifie la <strong>gestion du cycle de vie</strong> en nous permettant d&#x27;appliquer une logique de rapprochement distincte à chaque couche, offrant ainsi aux utilisateurs le contrôle des paramètres d&#x27;exécution (enfants) tout en conservant le contrôle administratif sur l&#x27;infrastructure de planification et de sécurité (parents).</p><ul><li><strong>Pourquoi en libre-service ?</strong></li></ul><p>Il était essentiel d&#x27;éliminer notre équipe comme goulot d&#x27;étranglement pour la configuration des référentiels. Notre mission était de concevoir une plateforme scalable et <strong>en libre-service</strong>, capable de prendre en charge divers cas d&#x27;utilisation. Nous avons constaté qu&#x27;il était impossible, compte tenu du nombre considérable de référentiels, de jouer le rôle de <strong>contrôleur d&#x27;accès</strong> pour chaque modification de configuration. Nous avons donc adopté une approche axée sur l&#x27;enablement : fournir les &quot;rails&quot; (l&#x27;infrastructure et les <strong>garde-fous</strong>), tout en donnant aux utilisateurs les moyens d&#x27;agir et de conduire les &quot;trains&quot; (l&#x27;exécution et la personnalisation). Nous pensons que cette évolution vers l&#x27;<strong>autonomie des équipes</strong> améliore considérablement la productivité en permettant aux utilisateurs d&#x27;adapter le système à leurs besoins opérationnels spécifiques.</p><ul><li><strong>Pourquoi le modèle d&#x27;opérateur Kubernetes ?</strong></li></ul><p>Comme indiqué précédemment, un principe de conception fondamental consistait à garantir que la plateforme soit entièrement <strong>en libre-service</strong>. Nous avions besoin d&#x27;un mécanisme automatisé pour capturer les intentions de l&#x27;utilisateur (par exemple, activer/désactiver les analyses, ajuster la fréquence de planification ou paramétrer les limites des ressources d&#x27;exécution) et transmettre instantanément ces modifications aux workflows sous-jacents. Afin d&#x27;anticiper les besoins futurs, le système devait aussi être facilement <strong>extensible</strong>.</p><p>Pour ce faire, nous avons développé un <strong>opérateur Kubernetes de gestion des dépendances</strong> personnalisé. En utilisant les <strong>CRD</strong> comme interface de configuration, nous avons établi une <strong>boucle de rapprochement native Kubernetes</strong>. Cet opérateur surveille en permanence l&#x27;état souhaité défini par l&#x27;utilisateur et orchestre automatiquement les mises à jour nécessaires de l&#x27;infrastructure de workflow. Ceci garantit un fonctionnement transparent et <strong>piloté par les événements</strong>, où la logique de la plateforme gère toute la complexité en arrière-plan.</p><ul><li><strong>Pourquoi concevoir une passerelle d’événements GitHub ?</strong></li></ul><p>L&#x27;adoption d&#x27;une <strong>architecture pilotée par les événements (EDA)</strong> était essentielle à la réactivité de la plateforme. Si les workflows Cron fournissaient une planification de base fiable, nous avions besoin d&#x27;agilité pour gérer les <strong>exécutions ad hoc</strong>, comme le déclenchement manuel d&#x27;analyses par les utilisateurs via le tableau de bord. Pour ce faire, nous avions besoin d&#x27;une <strong>passerelle d&#x27;ingestion</strong> dédiée afin de valider l&#x27;intégrité des données et acheminer intelligemment les requêtes.</p><p>Nous avons évalué les solutions existantes, notamment l&#x27;EventSource natif de GitHub pour Argo, mais nous avons identifié des risques importants liés à la <strong>surcharge opérationnelle</strong> et aux <strong>quotas stricts de l&#x27;API GitHub</strong> (par exemple, les limites de webhook par référentiel). Par conséquent, nous avons développé une passerelle personnalisée afin de découpler notre infrastructure de ces limitations.</p><p>Cette passerelle s&#x27;est avérée cruciale en tant que <strong>point de contrôle du trafic</strong> lors de notre migration. Elle a agi comme un commutateur, nous permettant d&#x27;effectuer un <strong>déploiement progressif et granulaire</strong> (transfert de trafic) de l&#x27;ancien système vers la nouvelle infrastructure. Ainsi, l&#x27;intégration de milliers de référentiels s&#x27;est déroulée de manière contrôlée et sans risque, plutôt que par une transition brutale.</p><p></p><h2><strong>Enseignements</strong></h2><p>Certains enseignements que nous avons tirés vont de pair avec le <a href="https://www.elastic.co/about/our-source-code">code source d&#x27;Elastic</a> :</p><ol><li><strong>Priorité au client</strong> : les plateformes sont conçues pour les utilisateurs. Il est donc essentiel de placer leurs besoins au cœur de nos priorités. Cela permet de concevoir une infrastructure et des applications performantes qui réduisent les obstacles pour les utilisateurs, simplifient le scaling de la plateforme et facilitent son adoption.</li><li><strong>Espace, temps</strong> : parfois, la voie de la facilité mène à des <strong>sables mouvants</strong>. Nous avons d&#x27;abord tenté d&#x27;optimiser le modèle de traitement séquentiel existant, mais cela n&#x27;a pas résolu nos problèmes ; au contraire, cela n&#x27;a fait qu&#x27;accroître la complexité et créer des zones d&#x27;ombre. La décision audacieuse de <strong>repenser l&#x27;architecture</strong> de la plateforme avec un traitement parallèle a nécessité un investissement initial important. Cependant, elle a finalement ouvert la voie à une croissance durable de la plateforme et a quasiment éliminé les tâches administratives quotidiennes fastidieuses.</li><li><strong>Informatique, dépendances</strong> : une plateforme ne peut pas fonctionner de manière isolée ; son succès dépend de la manière dont elle s&#x27;intègre dans un écosystème plus large. Dans notre cas, l&#x27;intégration à <strong>Backstage</strong> était essentielle, car elle constitue la source de référence pour une intégration fluide des services. De même, la connexion à <strong>Artifactory</strong> nous a permis de gérer efficacement les mises à jour des packages privés, et la liste des intégrations essentielles ne s&#x27;arrête pas là.</li><li><strong>Progrès, perfection SIMPLE</strong> : tout au long de la mise en œuvre, nous avons constamment testé nos hypothèses initiales et nous nous sommes adaptés aux nouveaux obstacles à mesure qu&#x27;ils apparaissaient. Plutôt que de nous laisser paralyser par le perfectionnisme, nous avons adopté une <strong>approche itérative</strong>, en relevant les défis les uns après les autres et en ajustant notre stratégie de migration aux conditions réelles.</li></ol><h2><strong>Prochaines étapes</strong></h2><p>La mise en place de la plateforme nous permet de nous consacrer à des tâches plus importantes qui contribueront à améliorer l&#x27;expérience utilisateur et l&#x27;efficacité de notre plateforme. En voici quelques exemples :<br/></p><ul><li><strong>Renforcer et garantir l&#x27;adoption de la fusion automatique</strong></li></ul><p>La fonctionnalité de fusion automatique accélère considérablement la rapidité de l&#x27;équipe en éliminant les tâches manuelles fastidieuses. Toutefois, il est essentiel de mettre en place des <strong>garde-fous</strong> stricts afin de garantir que cette rapidité accrue ne s&#x27;obtienne pas au détriment de la sécurité.<br/></p><ul><li><strong>Améliorer la visibilité sur l&#x27;expérience des utilisateurs finaux</strong></li></ul><p>L&#x27;une des priorités essentielles de notre feuille de route est l&#x27;amélioration de l&#x27;observabilité, non seulement au niveau de la plateforme, mais aussi <strong>du point de vue de l&#x27;utilisateur final</strong>. Si la collecte des indicateurs d&#x27;infrastructure est simple, la compréhension de l&#x27;expérience utilisateur réelle exige une analyse plus approfondie. Nous travaillons à la définition d&#x27;indicateurs clés de performance (KPI) centrés sur l&#x27;utilisateur afin que notre système de télémétrie puisse détecter les points de friction et les problèmes de performance <strong>avant</strong> qu&#x27;ils ne se transforment en plaintes d&#x27;utilisateurs.</p><ul><li><strong>Supprimer les obstacles à une plus grande adoption</strong></li></ul><p>Pour l&#x27;avenir, notre priorité est d&#x27;identifier et de lever les obstacles à l&#x27;adoption de la plateforme. Qu&#x27;il s&#x27;agisse de développer de nouvelles intégrations ou de déployer des fonctionnalités spécifiques, nous privilégions une planification fondée sur les données. Nous avons développé avec succès une plateforme conçue pour évoluer ; notre objectif est désormais <strong>d&#x27;en maximiser le potentiel</strong>.<br/></p><h2><strong>Le tableau d&#x27;ensemble</strong></h2><p>Le projet de workflows de gestion des dépendances illustre un principe plus large : <strong>lorsque vous devez scaler des outils open source au-delà de leur modèle de déploiement par défaut, les modèles natifs Kubernetes offrent une voie à suivre.</strong></p><p>En adoptant :</p><ul><li>Les CRD pour la configuration.</li><li>Les opérateurs pour la gestion du cycle de vie.</li><li>Une architecture basée sur les événements pour une meilleure réactivité</li><li>GitOps pour le déploiement.</li></ul><p>Nous avons conçu une orchestration qui scale indépendamment du nombre de référentiels gérés. Les performances d&#x27;analyse d&#x27;un seul référentiel restent identiques, que nous en gérions 100 ou 1 000.</p><p>Lorsqu&#x27;une CVE critique est annoncée, nous obtenons désormais des réponses en quelques minutes, et non plus en quelques heures. C&#x27;est ce qui fait la différence entre un goulot d&#x27;étranglement et un avantage concurrentiel.</p><h2><strong>Remerciements</strong></h2><p>Cette plateforme s&#x27;appuie sur d&#x27;excellents outils open source :</p><ul><li><strong>Kubebuilder</strong> : le framework open source que nous avons utilisé pour lancer nos opérateurs Kubernetes qui démarrent et orchestrent nos workflows. [<a href="https://github.com/kubernetes-sigs/kubebuilder">1</a>][<a href="https://book.kubebuilder.io/">2</a>]</li><li><strong>Backstage</strong> : le framework open source sur lequel nous avons construit notre catalogue de services et que nous utilisons comme source de référence. [<a href="https://github.com/backstage/backstage">1</a>][<a href="https://backstage.io/">2</a>]</li><li><strong>Argo Workflows et Argo Events</strong> : la suite open source que nous avons utilisée pour orchestrer des processus complexes et ajouter un traitement dynamique basé sur des événements. [<span class="unknown__pt__mark__1e5711660421">1</span>][<a href="https://argo-workflows.readthedocs.io/en/stable/">2</a>][<a href="https://argoproj.github.io/argo-events/">3</a>][<a href="https://github.com/argoproj/argo-events">4</a>]</li><li><strong>Renovate CLI</strong> : l&#x27;outil open source de gestion des dépendances qui traite nos référentiels. [<a href="https://github.com/renovatebot/renovate">1</a>][<a href="https://docs.renovatebot.com/getting-started/running/">2</a>]</li></ul><p>* Le modèle de tarification AWS Fargate a été utilisé comme référence pour le coût d&#x27;un seul pod, bien que nos charges de travail ne s&#x27;exécutent pas nécessairement sur AWS et s&#x27;exécutent sur des clusters Kubernetes complets.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Améliorez les performances de recherche avec 'best_compression']]></title>
            <description><![CDATA[Bien que 'best_compression' soit généralement considéré comme une fonctionnalité d'économie d'espace de stockage pour les cas d'utilisation d'Elastic Observability et d'Elastic Security, ce blog démontre son efficacité en tant que levier d'optimisation des performances pour la recherche.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/improve-elasticsearch-performance-best-compression</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/improve-elasticsearch-performance-best-compression</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sherry Ger, Ryan Eno]]></dc:creator>
            <pubDate>Fri, 23 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5141a8c2618337207d848ce16b258a86885955b2-1600x1034.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p></p><p>Lors de l&#x27;optimisation d&#x27;Elasticsearch pour des charges de travail à forte simultanéité, l&#x27;approche standard consiste à maximiser la RAM afin de conserver l&#x27;ensemble des documents de travail en mémoire et d&#x27;obtenir une faible latence de recherche. Par conséquent, <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules"><code>best_compression</code></a> est rarement l&#x27;option choisie pour les charges de travail de recherche, car il est principalement perçu comme une mesure d&#x27;économie d&#x27;espace pour les cas d&#x27;utilisation d&#x27;Elastic Observability et d&#x27;Elastic Security où l&#x27;efficacité du stockage est prioritaire.</p><p>Dans ce blog, nous démontrons que lorsque la taille de l&#x27;ensemble de données dépasse nettement le cache de page du système d&#x27;exploitation, <code>best_compression</code> améliore les performances de recherche et l&#x27;efficacité des ressources en réduisant le goulot d&#x27;étranglement des E/S.</p><h2><strong>La configuration</strong></h2><p>Notre cas d&#x27;utilisation est une application de recherche à forte simultanéité qui s&#x27;exécute sur des <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/ec-change-hardware-profile#ec-profiles-compute-optimized-arm">instances Elastic Cloud optimisées pour le processeur</a>.</p><ul><li>Volume de données : ~500 millions de documents</li><li>Infrastructure : 6 instances Elastic Cloud (Elasticsearch Service) (chaque instance : 1,76 To de stockage | 60 Go de RAM | 31,9 vCPU)</li><li>Rapport mémoire/stockage : la RAM peut recevoir environ 5 % du volume total de données</li></ul><h2><strong>Les symptômes : latence élevée</strong></h2><p>Nous avons constaté qu&#x27;aux alentours de 19:00, lorsque le nombre de requêtes augmentait fortement, la latence de recherche s&#x27;est considérablement dégradée. Comme le montrent les figures 1 et 2, lorsque le trafic atteignait un pic d&#x27;environ 400 requêtes par minute et par instance Elasticsearch, le temps de réponse moyen des requêtes chutait à plus de 60 ms.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f9c6cc1882e7db24336c65c54bbc1d38dcdb7fa3-697x311.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bbb705ae2fcd14c81d335bf322346caf3bf33765-996x618.png" style="width: 100%;" /><p>L&#x27;utilisation du processeur est restée relativement faible après le traitement initial des connexions, indiquant que le calcul n&#x27;était pas le facteur limitant.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cec15a28d2d22e9adedd2951bb2334b3717890a1-1494x730.png" style="width: 100%;" /><p>Une forte corrélation est apparue entre le volume de requêtes et les défauts de page. À mesure que les requêtes augmentaient, nous avons observé une hausse proportionnelle des défauts de page, avec un pic aux alentours de 400k/minute. Cela indique que l&#x27;ensemble de données actif ne pouvait pas être contenu dans le cache de pages.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/511f2f10300a9d10ba3d7a82b9a8c8d567ac5636-1492x678.png" style="width: 100%;" /><p>Parallèlement, l&#x27;utilisation du tas JVM semblait normale et saine. Cela a permis d&#x27;exclure les problèmes de récupération de mémoire et de confirmer que le goulot d&#x27;étranglement était lié aux entrées/sorties.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/336bbad638f866304358dba1d06ee987de0f23cf-1490x568.png" style="width: 100%;" /><h2><strong>Le diagnostic : I/O bound</strong></h2><p>Le système était limité par les E/S. <a href="https://www.elastic.co/blog/elasticsearch-caching-deep-dive-boosting-query-speed-one-cache-at-a-time">Elasticsearch s&#x27;appuie sur le cache de pages du système d&#x27;exploitation pour fournir les données d&#x27;index depuis la mémoire</a>. Lorsque l&#x27;index est trop volumineux pour le cache, les requêtes entraînent des lectures disque coûteuses. Bien que la solution classique consiste à effectuer un scaling horizontal (ajout de nœuds/RAM), nous souhaitions d&#x27;abord optimiser au maximum nos ressources existantes.</p><h2><strong>La solution</strong></h2><p>Par défaut, Elasticsearch utilise la compression <a href="https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)">LZ4</a> pour ses segments d&#x27;index, qui offre un bon compromis entre vitesse et taille. Nous avons émis l&#x27;hypothèse que le passage à <code>best_compression</code> (qui utilise <a href="https://en.wikipedia.org/wiki/Zstd">zstd</a>) réduirait la taille des index. Une empreinte mémoire plus faible permet d&#x27;intégrer une plus grande partie de l&#x27;index dans le cache de pages, moyennant une augmentation négligeable de la charge CPU (pour la décompression) au profit d&#x27;une réduction des E/S disque.</p><p>Pour activer <code>best_compression</code>, nous avons réindexé les données avec le paramètre d&#x27;index <code>index.codec: best_compression</code>. Sinon, le même résultat pourrait être obtenu en fermant l&#x27;index, en réinitialisant le codec d&#x27;index à <code>best_compression</code>, puis en effectuant une fusion de segments.</p><pre><code>POST my-index/_close
PUT my-index/_settings
{
    "codec": "best_compression"
}
  
POST my-index/_open  
POST my-index/_forcemerge?max_num_segments=1</code></pre><h2><strong>Résultats</strong></h2><p>Les résultats ont confirmé notre hypothèse : l&#x27;amélioration de l&#x27;efficacité du stockage s&#x27;est directement traduite par une augmentation substantielle des performances de recherche sans augmentation concomitante de l&#x27;utilisation du processeur.</p><p>L&#x27;application de <code>best_compression</code> a réduit la taille de l&#x27;index d&#x27;environ 25 %. Bien qu&#x27;inférieure à la réduction observée dans les données de log répétitives, cette réduction de 25 % a effectivement augmenté la capacité de notre cache de pages dans les mêmes proportions.</p><p>Lors du test de charge suivant (à partir de 17:00), le trafic était encore plus élevé, avec un pic de 500 requêtes par minute et par nœud Elasticsearch.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fc1902f396cb2115c0013155ad07f6eb87389c60-660x309.png" style="width: 100%;" /><p>Malgré la charge plus élevée, l&#x27;utilisation du processeur était inférieure à celle de l&#x27;exécution précédente. L&#x27;utilisation élevée dans le test précédent était probablement due à la surcharge liée à la gestion excessive des défauts de page et à la gestion des E/S de disque.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/15699ef4c65b3f0a9f8a3e1bae8bb18f7b647025-819x352.png" style="width: 100%;" /><p>Surtout, le nombre de défauts de page diminue de manière significative. Même à un débit plus élevé, les erreurs se situent autour de &lt;200k par minute, contre &gt;300k dans le test de référence.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f76ca967976d740af88a9359b66041701abb46fc-764x340.png" style="width: 100%;" /><p>Bien que les résultats concernant les défauts de page soient encore loin d&#x27;être optimaux, le temps de réponse aux requêtes a été réduit d&#x27;environ 50 %, se maintenant sous la barre des 30 ms même en cas de charge plus importante.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/750ec1c59b8eb5069aed4c066d856ecea82d5bca-620x311.png" style="width: 100%;" /><p></p><h2><strong>Conclusion : best_compression pour la recherche</strong></h2><p>Pour les cas d&#x27;utilisation de recherche où le volume de données dépasse la mémoire physique disponible, <code>best_compression</code> est un levier puissant d&#x27;optimisation des performances.</p><p>La solution classique aux erreurs de cache consiste à scaler pour augmenter la RAM. Cependant, en réduisant l&#x27;empreinte de l&#x27;index, nous avons atteint le même objectif : maximiser le nombre de documents dans le cache de pages. Notre prochaine étape consistera à explorer l&#x27;<a href="https://www.elastic.co/blog/space-savings-a-lesser-known-benefit-of-index-sorting-in-elasticsearch"><strong>index trié</strong></a> afin d&#x27;optimiser davantage l&#x27;espace de stockage et d&#x27;améliorer encore les performances de nos ressources existantes.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Agent Builder est maintenant en disponibilité générale : créez des agents contextuels en quelques minutes]]></title>
            <description><![CDATA[Agent Builder est maintenant en disponibilité générale. Découvrez comment il vous permet de développer rapidement des agents d'IA contextuels.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/agent-builder-elastic-ga</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/agent-builder-elastic-ga</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Anish Mathur, Evan Castle]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6840eb7dbb884055ab0e965dcfd614fec54936af-2210x1440.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Nous sommes ravis d&#x27;annoncer la disponibilité générale d&#x27;Agent Builder dans Elastic Cloud Serverless et dans la prochaine version 9.3. Agent Builder exploite la puissance d&#x27;Elasticsearch comme plateforme d&#x27;ingénierie du contexte pour développer rapidement des agents d&#x27;IA contextuels et axés sur les données.</p><p>Les agents gagnent du terrain en raison de leur potentiel d&#x27;amélioration de l&#x27;efficacité et de l&#x27;expérience client. Mais dans la pratique, il est difficile de fournir aux agents le bon contexte, en particulier lorsqu&#x27;ils travaillent sur des données d&#x27;entreprise hétérogènes et non structurées. Les développeurs doivent gérer les outils, les prompts, l&#x27;état, la logique de raisonnement, les modèles, et surtout récupérer un contexte pertinent à partir des sources métier pour garantir des résultats et des actions précis. Elastic Agent Builder fournit ces composants essentiels pour développer des agents sécurisés, fiables et contextuels.</p><h2>Fonctionnalités principales d&#x27;Agent Builder</h2><p>Agent Builder est le résultat des investissements à long terme d&#x27;Elastic dans la pertinence de la recherche et la génération augmentée par récupération, et contribue à faire d&#x27;Elasticsearch la meilleure base de données vectorielle pour simplifier le développement d&#x27;agents d&#x27;IA contextuels et axés sur les données.</p><p>Agent Builder vous permet de :</p><ul><li>Commencer immédiatement avec un agent conversationnel intégré capable de répondre aux questions, d&#x27;effectuer des analyses et de mener des investigations sur n&#x27;importe quelles données dans Elasticsearch.</li><li>Passer rapidement des données non structurées complexes à un agent personnalisé grâce à une expérience de développement basée sur la configuration.</li><li>Bénéficier de la pertinence d&#x27;une recherche hybride de pointe grâce à ES|QL intégré ou à des outils personnalisés pour améliorer la qualité du contexte et la fiabilité des agents.</li><li>Exécuter des workflows complexes (préversion) sous forme d&#x27;outils réutilisables pour enrichir les données, mettre à jour les enregistrements, envoyer des messages et plus encore pour l&#x27;automatisation basée sur des règles.</li><li>Vous connecter à des sources de données externes à Elasticsearch à l&#x27;aide de workflows et de MCP pour corréler et combiner le contexte pour les agents.</li><li>Intégrer à n&#x27;importe quel framework agentique ou d&#x27;application à l&#x27;aide d&#x27;outils intégrés et personnalisés exposés via MCP, et possibilité de se connecter à un MCP externe (préversion), prise en charge d&#x27;A2A et support technique API complet.</li><li>Étendre les capacités d&#x27;Agent Builder avec l&#x27;intégration de solutions tierces comme LlamaIndex pour le traitement complexe de documents ou Arcade.dev pour un accès sécurisé et structuré aux outils.</li></ul><p>Pour étendre les fonctionnalités d&#x27;Agent Builder, nous lançons Elastic Workflows, notre nouvelle solution d&#x27;automatisation basée sur des règles, actuellement disponible en préversion technique. Pour les tâches organisationnelles, les agents ont parfois besoin de la certitude et de la fiabilité des actions basées sur des règles, qui sont souvent nécessaires pour mettre en œuvre une logique métier spécifique. Elastic Workflows offre aux agents une méthode simple et déclarative pour orchestrer des systèmes internes et externes afin d&#x27;effectuer des actions, de collecter des données et du contexte, et de les transformer. Entièrement composables, pilotés par les événements et flexibles, les workflows peuvent être exposés comme outils à un agent via MCP.</p><h2>Passez des données à l&#x27;agent en quelques minutes</h2><p>Le développement d&#x27;agents peut prendre des semaines de travail préparatoire pour consolider des datastores distincts, construire des pipelines manuels, optimiser les requêtes et gérer une orchestration complexe. Agent Builder réduit le temps de développement des agents en supprimant le besoin de datastores séparés, de bases vectorielles, de pipelines RAG, de couches de recherche, de traducteurs de requêtes et d&#x27;orchestrateurs d&#x27;outils, vous permettant ainsi de vous concentrer sur la logique de l&#x27;agent et la livraison de l&#x27;application.</p><p>Agent Builder intègre nativement les primitives de la plateforme Elasticsearch pour accélérer le développement d&#x27;agents.</p><ul><li>Commencez avec un agent conversationnel intégré qui peut immédiatement discuter et raisonner avec vos données indexées.</li><li>Intégrez des agents dans des applications, des tableaux de bord ou des systèmes CI/CD avec un accès interactif via Kibana, des API, ou MCP et A2A.</li><li>Utilisez des outils par défaut pour comprendre la structure de vos données, sélectionner l&#x27;index approprié, générer des requêtes hybrides, sémantiques et structurées optimisées, et créer des visualisations configurables à l&#x27;aide d&#x27;ES|QL basées sur des prompts en langage naturel.</li></ul><p>Pour aller plus loin, essayez une <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">procédure pas à pas</a> complète.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b55b63eae40f72952967cc8f3ea4df4cd62d7d70-1080x608.gif" style="width: 100%;" /><h2>Développez sur Elasticsearch, une plateforme de données complète pour l&#x27;ingénierie du contexte</h2><p>En matière d&#x27;agents d&#x27;IA, la qualité du contexte est essentielle pour un raisonnement efficace et pour limiter les risques d&#x27;hallucinations. Dans de nombreux cas, les données métier nécessaires à l&#x27;exécution d&#x27;une tâche constituent l&#x27;élément de contexte le plus crucial. Elasticsearch, base de données vectorielle hautement scalable et leader en matière de pertinence, offre déjà de nombreuses primitives performantes d&#x27;ingénierie du contexte. L&#x27;ingénierie du contexte va au-delà de la simple génération augmentée par récupération : elle permet de personnaliser et de dimensionner la manière dont les données sont extraites, classées, filtrées et présentées aux agents, contribuant ainsi à réduire le bruit et l&#x27;ambiguïté.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/419b9b6f13739e0a8983249d8ac31478e73dac89-1600x901.png" style="width: 100%;" /><p>Elasticsearch fournit un moteur de contexte qui combine la recherche lexicale, la recherche vectorielle et le filtrage structuré pour la récupération, ce qui <a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">améliore considérablement les performances des LLM</a> en garantissant que le modèle opère sur un contexte pertinent et précis. Cette fonctionnalité est prise en charge par la récupération agentique, ainsi que par des outils intégrés et une logique de recherche qui sélectionnent automatiquement les index appropriés et transforment le langage naturel en requêtes optimisées pour le contexte.</p><p>Avec Agent Builder, vous avez l&#x27;assurance que les agents reçoivent en priorité le contexte le plus pertinent grâce à des options de contrôle de la pertinence et du classement afin d&#x27;affiner la logique de notation, de classement et de filtrage. Elasticsearch vous permet de contrôler ce qui est important, pourquoi c&#x27;est important et comment l&#x27;ordre de priorité est établi, au lieu de vous fier à un comportement de récupération opaque. L&#x27;ensemble repose sur Elasticsearch, une plateforme de données scalable qui permet de stocker et de gérer toutes vos données (texte, vecteurs, métadonnées, logs, etc.) sur une seule et même plateforme, simplifiant ainsi la gestion du contexte pour les agents.</p><h2>Exécutez des workflows complexes en tant qu&#x27;outils réutilisables</h2><p>Si les agents d&#x27;IA permettent de raisonner sur des tâches complexes, l&#x27;automatisation repose en grande partie sur l&#x27;exécution fiable d&#x27;actions basées sur des règles qui appliquent une logique métier spécifique. Elastic Workflows offre une méthode simple et déclarative pour orchestrer les systèmes internes et externes afin d&#x27;effectuer des actions, collecter du contexte ou des données et les intégrer aux agents. Définis en YAML, les workflows sont entièrement composables de manière à les rendre aussi simples ou complexes que l&#x27;exige la tâche à accomplir. Les agents disposent ainsi d&#x27;un moyen efficace d&#x27;interagir avec la plateforme et les solutions Elasticsearch, de même qu&#x27;avec des applications tierces.</p><p>L&#x27;intégration d&#x27;un workflow avec Agent Builder peut se faire en trois étapes (prérequis : activez les workflows avec les détails fournis <a href="https://github.com/elastic/workflows">ici</a>)</p><p>1. Créez et enregistrez un nouveau workflow à l&#x27;aide de l&#x27;éditeur simple basé sur YAML avec autocomplétion et tests intégrés.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/308888bf3d2fa013f9391a55be6a6fbd458b6dac-1600x998.png" style="width: 100%;" /><p>2. Créez un nouvel outil dans Agent Builder avec le type &quot;Workflow&quot; et fournissez une description pour aider l&#x27;agent à déterminer quand utiliser l&#x27;outil de workflow.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c04810d30d226112c3610bd58e208607b213fc3d-1600x945.png" style="width: 100%;" /><p>3. Ajoutez l&#x27;outil de workflow à votre agent personnalisé.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/724cd4ac93c46efb0d339fd140e5caf138f8150f-1600x948.png" style="width: 100%;" /><p>4. Et voilà ! L&#x27;agent peut maintenant déclencher le workflow directement depuis une conversation.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8dfdd726ab89e31c48b79372650ce33946713dca-1600x929.png" style="width: 100%;" /><h2>Votre agent, vos règles</h2><p>Agent Builder ne vous enferme pas dans un seul paradigme de développement. Au contraire, il est conçu pour permettre des approches de développement ouvertes et flexibles pour les agents avec un contrôle total des données, de la pertinence, des modèles, de l&#x27;interopérabilité, de la sécurité et de la conception des agents.</p><p>Les définitions d&#x27;agents personnalisés vous permettent de choisir précisément les outils auxquels un agent peut accéder, d&#x27;intégrer des prompts système personnalisés, d&#x27;adapter ses instructions et de définir des limites de sécurité. Les agents restent indépendants du modèle, ce qui vous permet de configurer avec flexibilité un LLM de votre choix, qu&#x27;il soit natif ou issu de l&#x27;écosystème étendu, sans être lié à un fournisseur unique.</p><p>Créez des outils extensibles qui encapsulent la logique spécifique au domaine (par exemple, des filtres d&#x27;index spécifiques, des jointures ES|QL, des pipelines analytiques) et sécurisez leur utilisation en production. La prise en charge API complète assure l&#x27;interopérabilité avec d&#x27;autres frameworks d&#x27;agents, grâce à une compatibilité native avec le protocole MCP (Model Context Protocol). L&#x27;intégration A2A vous permet d&#x27;exposer vos agents Elastic à d&#x27;autres frameworks, services et applications clientes, en réutilisant la même logique d&#x27;ingénierie des données et du contexte.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5e903ba24ffb3f40231e901f63bd494c89cb7757-1600x1004.png" style="width: 100%;" /><p>Agent Builder permet un développement flexible et ouvert, et il est conçu pour s&#x27;intégrer facilement aux frameworks et plateformes d&#x27;agents les plus populaires. Ces intégrations peuvent être essentielles pour fournir des agents efficaces. Comme l&#x27;explique <strong>Sam Partee, cofondateur d&#x27;Arcade.dev</strong>,</p><p><em>&quot;Les systèmes agentiques échouent aujourd&#x27;hui, car la connexion de l&#x27;IA aux outils et aux données est complexe. Elastic Agent Builder avec Arcade.dev offre aux développeurs un moyen structuré et sécurisé de gérer la manière dont les agents récupèrent le contexte, raisonnent et agissent, permettant ainsi de passer de la démo à la phase de production.&quot;</em></p><p>Agent Builder tire également parti de l&#x27;extensibilité d&#x27;Elasticsearch pour gérer des données complexes. Comme le décrit <strong>Jerry Liu, PDG de LlamaIndex </strong>,</p><p><em>&quot;L&#x27;extraction du contexte d&#x27;entreprise à partir de sources de données non structurées est essentielle à la création d&#x27;agents performants. Elastic Agent Builder, associé au traitement de documents complexes de LlamaIndex, renforce la couche de contexte critique, aidant les équipes à récupérer, traiter et préparer les données afin que les agents puissent raisonner avec plus de précision et obtenir de meilleurs résultats.&quot;</em></p><h2>Que pouvez-vous construire ?</h2><p>Agent Builder est déjà exploité dans de nombreux cas d&#x27;utilisation. Vous trouverez ci-dessous quelques exemples et architectures de référence pour vous familiariser avec les agents :</p><ul><li><strong>Automatiser l&#x27;infrastructure</strong> : dans les scénarios de support, les agents sont utilisés pour lire, analyser et dialoguer, mais jusqu&#x27;à présent, ils ne peuvent pas interagir directement avec l&#x27;infrastructure qu&#x27;ils sont appelés à gérer. L&#x27;équipe d&#x27;ingénierie d&#x27;Elastic a développé un agent pour la <a href="https://www.elastic.co/search-labs/blog/agent-builder-augmented-infrastructure">gestion automatisée de l&#x27;infrastructure</a> dans le cadre d&#x27;un hackathon. L&#x27;agent enquête activement sur les problèmes liés à l&#x27;infrastructure des applications et prend des mesures automatisées. Il utilise des workflows pour optimiser les configurations, répondre aux problèmes et scaler les ressources, le tout basé sur une compréhension intelligente des logs d&#x27;infrastructure.</li><li><strong>Analyse des menaces de sécurité</strong> : un agent de vulnérabilité de sécurité a été développé avec Elastic Agent Builder, MCP et Elasticsearch. Il automatise l&#x27;analyse des menaces en corrélant les données de sécurité internes avec les renseignements sur les menaces externes. L&#x27;agent effectue une recherche sémantique sur les incidents et configurations historiques, enrichit les résultats avec des données Internet en temps réel et applique un raisonnement LLM pour évaluer la pertinence environnementale, hiérarchiser les risques et proposer des mesures correctives concrètes. Voir l&#x27;<a href="https://www.elastic.co/search-labs/blog/agent-builder-mcp-reference-architecture-elasticsearch">architecture de référence</a><strong>.</strong></li><li><strong>Support technique client</strong> : les agents peuvent effectuer de nombreuses tâches de support, notamment la synthèse des cas, la déduplication et la création de tickets, ainsi que des investigations techniques approfondies. Agent Builder facilite ces opérations grâce à une recherche hybride en plusieurs étapes permettant de trouver uniquement les problèmes, solutions et procédures les plus pertinents, de formuler des hypothèses sur les causes profondes et de proposer des plans de remédiation. Agent Builder peut simplifier l&#x27;architecture des <a href="https://www.elastic.co/blog/generative-ai-customer-support-elastic-support-assistant">systèmes de support</a> complexes et accélérer les délais de livraison.</li><li><strong>Découverte de produits et de contenus</strong> : Agent Builder simplifie le processus d&#x27;<a href="https://www.elastic.co/search-labs/blog/build-voice-agents-elastic-agent-builder">exposition de catalogues produits complexes pour des expériences conversationnelles</a>, tout en permettant aux organisations de conserver la flexibilité nécessaire pour inclure leur propre logique métier et leurs propres exigences.</li><li><strong>Créez le vôtre</strong> : participez au <a href="https://elasticsearch.devpost.com/">hackathon Agent Builder,</a> qui se déroulera du 22 janvier au 27 février 2026. Collaborez avec la communauté pour créer des agents d&#x27;IA contextuels à plusieurs étapes qui combinent la recherche, les workflows, les outils et le raisonnement pour automatiser des tâches concrètes*</li></ul><h2>Commencez à créer des agents personnalisés dès maintenant</h2><p>Commencez avec un <a href="https://cloud.elastic.co/registration?onboarding_token=search&amp;pg=en-enterprise-search-page">essai Elastic Cloud</a>, et consultez la documentation <a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">ici</a>. Pour les clients existants, Agent Builder est disponible dans Cloud Serverless et avec le niveau Enterprise dans Elastic Cloud Hosted et autogéré.</p><p>* <a href="https://elasticsearch.devpost.com/rules">Cliquez ici</a> pour connaître les modalités, conditions et critères d&#x27;éligibilité pour le hackathon</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Création d'agents vocaux avec Elastic Agent Builder]]></title>
            <description><![CDATA[Découverte du fonctionnement des agents vocaux et création d’un agent vocal avec Elastic Agent Builder et LiveKit.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/build-voice-agents-elastic-agent-builder</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/build-voice-agents-elastic-agent-builder</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Kenneth Kreindler]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/43ceabb9e2c0966261c188bd40e03178d5a91e5c-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>L’IA est restée enfermée dans une boîte en verre. Vous tapez des commandes, elle répond par du texte. Et c’est tout. C’est utile, mais distant. Comme observer quelqu’un derrière un écran. Cette année, 2026, marquera un tournant : les entreprises briseront cette vitre pour intégrer des agents d’IA dans leurs produits – là où ils apportent une vraie valeur.</p><p>L’un des moyens de briser la vitre : adopter des <em>agents vocaux</em>, autrement dit des agents IA capables de comprendre la voix humaine et de produire un son de synthèse. Grâce à l’essor des transcriptions à faible latence, des modèles de langage de grande taille (LLM) rapides et des systèmes de synthèse vocale au rendu naturel, cette vision devient réalité.</p><p>Pour réellement créer de la valeur, les agents vocaux doivent aussi avoir accès aux données métier. Dans ce billet, nous verrons comment fonctionnent les agents vocaux et comment en créer un pour ElasticSport, une boutique fictive d’équipements de sport de plein air, à l’aide de <a href="https://livekit.io/">LiveKit</a> et <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>. Notre agent vocal sera sensible au contexte et s’appuiera sur nos données.</p><h2>Fonctionnement</h2><p>Le monde des agents vocaux repose sur deux grands paradigmes : le premier s’appuie sur des modèles de conversion vocale directe (speech-to-speech), le second sur une chaîne vocale composée de reconnaissance vocale, de LLM et de synthèse vocale. Les modèles speech-to-speech ont leurs avantages, mais les chaînes vocales permettent une personnalisation bien plus poussée des technologies employées et de la gestion du contexte, ainsi qu’un contrôle plus fin du comportement de l’agent. Nous allons nous concentrer sur le modèle basé sur la chaîne vocale.</p><h3>Composants clés</h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b237501903f9c3a71fe1b7755c3990e40c5495c8-1600x653.png" style="width: 100%;" /><h4>Transcription (reconnaissance vocale)</h4><p>La transcription est le point d’entrée de la chaîne vocale. Le composant de transcription reçoit des trames audio brutes en entrée, convertit la voix en texte, puis restitue ce texte en sortie. Le texte transcrit est mis en mémoire tampon jusqu’à ce que le système détecte la fin de la prise de parole de l’utilisateur – c’est alors que la génération par le LLM démarre. Plusieurs prestataires tiers proposent des transcriptions à faible latence. Lors de votre sélection, tenez compte de la latence et de la précision de transcription, et vérifiez que le fournisseur prend en charge les transcriptions en flux continu.</p><p></p><p>Exemples d’API tierces : <a href="https://www.assemblyai.com/">AssemblyAI</a>, <a href="https://deepgram.com/product/speech-to-text">Deepgram</a>, <a href="https://platform.openai.com/docs/guides/realtime-transcription">OpenAI</a>, <a href="https://elevenlabs.io/speech-to-text">ElevenLabs</a></p><h4>Détection de prise de parole</h4><p>La détection de prise de parole est le composant de la chaîne qui identifie la fin de l’intervention de l’utilisateur, déclenchant ainsi la génération. Une méthode courante consiste à utiliser un modèle de détection d’activité vocale (VAD), comme <a href="https://github.com/snakers4/silero-vad">Silero VAD</a>. Le VAD s’appuie sur le niveau d’énergie du signal audio pour détecter la présence de parole et identifier la fin de l’intervention. Cependant, un VAD seul ne peut pas distinguer une pause de la fin d’une prise de parole. C’est pourquoi on l’associe souvent à un modèle de fin d’énoncé, capable de prédire si l’utilisateur a terminé de parler, en se basant sur la transcription provisoire ou l’audio brut.</p><p>Exemples (Hugging Face) : <a href="https://huggingface.co/livekit/turn-detector">livekit/turn-detector</a>, <a href="https://huggingface.co/pipecat-ai/smart-turn-v3">pipecat-ai/smart-turn-v3</a></p><h4>Agent</h4><p>L’agent constitue le cœur de la chaîne vocale. Il est chargé de comprendre l’intention, de récupérer le bon contexte et de formuler une réponse sous forme de texte. <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>, avec ses fonctions de raisonnement intégrées, sa bibliothèque d’outils et son intégration aux workflows, permet de créer un agent capable d’exploiter vos données et d’interagir avec des services externes.</p><h4>LLM (texte à texte)</h4><p>Pour choisir un LLM dans Elastic Agent Builder, deux critères principaux sont à prendre en compte : les benchmarks de raisonnement du LLM et le temps jusqu’au premier jeton (TTFT).</p><p>Les benchmarks de raisonnement mesurent la capacité du LLM à produire des réponses pertinentes. Les benchmarks à privilégier sont ceux qui évaluent la cohérence des conversations à plusieurs tours et les capacités cognitives, comme MT-Bench et le jeu de données Humanity’s Last Exam, respectivement.</p><p>Les benchmarks TTFT évaluent la rapidité avec laquelle le modèle génère son premier jeton en sortie. Il existe d’autres types de benchmarks de latence, mais le TTFT est particulièrement crucial pour les agents vocaux, car la synthèse vocale peut démarrer dès réception du premier jeton. Résultat : une latence réduite entre les prises de parole et une conversation plus naturelle.</p><p>Il faut souvent faire un compromis entre ces deux critères, car les modèles plus rapides obtiennent généralement de moins bons résultats aux tests de raisonnement.</p><p>Exemples (Hugging Face) : <a href="https://huggingface.co/openai/gpt-oss-20b">openai/gpt-oss-20b</a>, <a href="https://huggingface.co/openai/gpt-oss-120b">openai/gpt-oss-120b</a></p><h4>Synthèse (texte à la parole)</h4><p>La dernière étape de la chaîne consiste à convertir le texte en parole grâce à un modèle de synthèse vocale. Ce composant est chargé de convertir le texte généré par le LLM en parole audible. Comme pour les LLM, la latence est un critère important lors du choix d’un fournisseur de synthèse vocale. La latence de la synthèse vocale se mesure au temps jusqu’au premier octet (TTFB). C’est le délai nécessaire pour recevoir le tout premier octet audio. Un TTFB plus court permet aussi de réduire la latence entre les prises de parole.</p><p>Exemples : <a href="https://elevenlabs.io/text-to-speech-api">ElevenLabs</a>, <a href="https://cartesia.ai/sonic">Cartesia</a>, <a href="https://www.rime.ai/">Rime</a></p><h4>Construction du pipeline vocal</h4><p>Elastic Agent Builder peut s’intégrer dans une chaîne vocale à différents niveaux :</p><ol><li>Outils Agent Builder uniquement : reconnaissance vocale → LLM (avec outils Agent Builder) → synthèse vocale</li><li>Agent Builder en tant que MCP : reconnaissance vocale → LLM (avec accès Agent Builder via MCP) → synthèse vocale</li><li>Agent Builder comme noyau central : reconnaissance vocale → Agent Builder → synthèse vocale</li></ol><p>Pour ce projet, j’ai choisi d’utiliser Agent Builder comme solution centrale. Cette approche permet de tirer pleinement parti des fonctionnalités d’Agent Builder et des workflows. Le projet s’appuie sur LiveKit pour orchestrer la reconnaissance vocale, la détection de prise de parole et la synthèse vocale. Il implémente également un nœud LLM personnalisé, directement intégré à Agent Builder.</p><h2>Agent vocal de support technique Elastic</h2><p>Nous allons créer un agent vocal de support personnalisé pour une boutique de sport fictive, appelée ElasticSport. Les clients pourront appeler la ligne d’assistance, demander des recommandations de produits, consulter les fiches produit, vérifier le statut de leurs commandes, et recevoir les informations par message texte. Pour cela, nous devons commencer par configurer un agent personnalisé et créer des outils permettant d’exécuter des requêtes Elasticsearch Query Language (ES|QL) ainsi que des workflows.</p><a href="https://videos.elastic.co/watch/jpX6D21URZuh3PtXVV7Ks6">Watch video on Vidyard</a><h3>Configuration de l&#x27;agent</h3><h4>Invite</h4><p>L’invite détermine la personnalité que doit adopter l’agent et la façon dont il doit répondre. Il existe également quelques invites spécifiques à la voix, qui garantissent une synthèse vocale fluide et permettent de gérer élégamment les incompréhensions.</p><pre><code>You are a Sales Assistant at ElasticSport, an outdoor sport shop specialized in hiking and winter equipment. 

[Profile]
- name: Iva
- company: ElasticSport
- role: Sales Assistant
- language: en-GB
- description: ElasticSport virtual sales assistant

[Context]
- Ask clarifying questions to understand the context.
- Use available tools to answer the user's question.
- Use the knowledge base to retrieve general information

[Style]
- Be informative and comprehensive.
- Maintain a professional, friendly and polite tone.
- Mimic human behavior and speech patterns.
- Be concise. Do not over explain initially

[Response Guideline]
- Present dates in spelled-out month date format (e.g., January fifteenth, two thousand and twenty-four).
- Avoid the use of unpronounceable punctuation such as bullet points, tables, emojis.
- Respond in plain text, avoid any formatting.
- Spell out numbers as words for more natural-sounding speech.
- Respond in short and concise sentences. Responses should be 1 or 2 sentences long.

[ERROR RECOVERY]
### Misunderstanding Protocol
1. Acknowledge potential misunderstanding
2. Request specific clarification</code></pre><h4>Workflows</h4><p>Nous allons ajouter un petit workflow permettant d’envoyer un SMS via l’API de messagerie de Twilio. Ce workflow sera exposé à l’agent personnalisé sous forme d’outil, afin que l’agent puisse envoyer un SMS à l’appelant pendant l’appel. Cela permet à l’appelant, par exemple, de demander : « Pouvez-vous m’envoyer plus de détails sur <em>X</em> par SMS ? »</p><pre><code>name: send sms
enabled: true
triggers:
  - type: manual
inputs:
  - name: message
    type: string
    description: The message to send to the phone number.

  - name: phone_number
    type: string
    description: The phone number to send the message to.

consts:
  TWILIO_ACCOUNT: "****"
  BASIC_AUTH: "****"
  FROM_PHONE_NNUMBER: "****"
steps:
  - name: http_step
    type: http
    with:
      url: https://api.twilio.com/2010-04-01/Accounts/{{consts.TWILIO_ACCOUNT}}/Messages.json
      method: POST
      headers:
        Content-Type: application/x-www-form-urlencoded
        Authorization: Basic {{consts.BASIC_AUTH | base64_encode}}
      body: From={{consts.FROM_PHONE_NNUMBER}}&To={{inputs.phone_number}}&Body={{inputs.message}}
      timeout: 30s</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b057e71b0a7c50eb3da47cd4f95e77ec7b4c6126-1600x1245.png" style="width: 100%;" /><h4>Outils ES|QL</h4><p>Les outils suivants permettent à l’agent de fournir des réponses pertinentes, basées sur des données réelles. Le dépôt d’exemple contient un script d’initialisation de Kibana avec des jeux de données produits, commandes et base de connaissances.</p><ul><li><strong>Product.search</strong></li></ul><p>Le jeu de données produit contient 65 produits fictifs. Voici un exemple de document :</p><pre><code>{
      "sku": "ort3M7k",
      "name": "Ortovox Free Rider 26 Backpack",
      "price": 189,
      "currency": "USD",
      "image": "https://via.placeholder.com/150",
      "description": "The Ortovox Free Rider 26 is a technical freeride backpack with a dedicated safety compartment and diagonal ski carry system. Perfect for backcountry missions.\n\nKey Features:\n- 26L capacity\n- Diagonal ski carry system\n- Safety equipment compartment\n- Helmet holder\n- Hydration system compatible",
      "category": "Accessories",
      "subCategory": "Backpacks",
      "brand": "Ortovox",
      "sizes": ["One Size"],
      "colors": ["Black", "Blue", "Orange"],
      "materials": ["Nylon", "Polyester"]
    }</code></pre><p>Les champs « name » et « description » sont mappés sur <code>semantic_text</code>, ce qui permet au LLM d’effectuer une recherche sémantique via ES|QL pour retrouver les produits pertinents. La requête de recherche hybride effectue une correspondance sémantique sur les deux champs, en appliquant une pondération légèrement supérieure au champ « name » grâce à un boost.</p><p>La requête récupère d’abord les 20 meilleurs résultats, classés selon leur score de pertinence initial. Ces résultats sont ensuite reclassés en fonction de leur champ « description » à l’aide du modèle d’inférence <code>.rerank-v1-elasticsearch</code> , puis réduits aux cinq produits les plus pertinents.</p><pre><code>type: ES|QL
toolId: products.search
description: Use this tool to search through the product catalogue by keywords.
query: |
    FROM products
        METADATA _score
      | WHERE
          MATCH(name, ?query, {"boost": 0.6}) OR
            MATCH(description, ?query, {"boost": 0.4})
      | SORT _score DESC
      | LIMIT 20
      | RERANK ?query
            ON description
            WITH {"inference_id": ".rerank-v1-elasticsearch"}
      | LIMIT 5

parameters:
    query: space separated keywords to search for in catalogue</code></pre><ul><li><strong>Knowledgebase.search</strong></li></ul><p>Les jeux de données de la base de connaissances contiennent des documents structurés comme suit, avec les champs de titre et de contenu stockés sous forme de texte sémantique :</p><pre><code>{
        id: "8273645",
        createdAt: "2025-11-14",
        title: "International Orders",
        content: `International orders are processed through our international shipping partner. Below are the countries we ship to and average delivery times.
        Germany: 3-5 working days
        France: 3-5 working days
        Italy: 3-5 working days
        Spain: 3-5 working days
        United Kingdom: 3-5 working days
        United States: 3-5 working days
        Canada: 3-5 working days
        Australia: 3-5 working days
        New Zealand: 3-5 working days
        `
}</code></pre><p>L’outil utilise une requête similaire à celle de <code>product.search</code> :</p><pre><code>type: "ES|QL"
toolId: knowledgebase.search
description: Use this tool to search the knowledgebase.
query: |
  FROM knowledge_base
    METADATA _score
  | WHERE
      MATCH(title, ?query, {"boost": 0.6}) OR
      MATCH(content, ?query, {"boost": 0.4})
  | SORT _score DESC
  | LIMIT 20
  | RERANK ?query
      ON content
      WITH {"inference_id": ".rerank-v1-elasticsearch"}
  | LIMIT 5

parameters:
  query: space separated keywords or natural language phrase to semantically search for in the knowledge base</code></pre><ul><li><strong>Orders.search</strong></li></ul><p>Le dernier outil que nous allons ajouter permet de récupérer les commandes à partir de <code>order_id</code> :</p><pre><code>type: "ES|QL"
toolId: order.search
description: Use this tool to retrieve an order by its ID.
query: |
  FROM orders
    METADATA _score
  | WHERE order_id == ?order_id
  | SORT _score DESC
  | LIMIT 1

parameters:
  order_id: "the ID of the order"</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d22bdd540a95b5a9c2bd5f308620835e8e6f7ecb-1600x1361.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d91709a50fb5391876b714885242d998b2b21027-1600x1443.png" style="width: 100%;" /><p>Une fois l’agent configuré et les workflows ainsi que les outils ES|QL associés, vous pouvez tester l’agent dans Kibana.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8691f41624247a6b1352d158c970031e1426ce5e-1600x1056.png" style="width: 100%;" /><p>Au-delà de l’agent de support ElasticSport, l’agent, les workflows et les outils peuvent être adaptés à d’autres cas d’usage, comme un agent commercial pour qualifier des prospects, un agent de dépannage à domicile, un système de réservation pour restaurant ou encore un agent de planification de rendez-vous.</p><p></p><p>La dernière étape consiste à connecter l’agent que nous venons de créer à LiveKit, à la synthèse vocale et à la reconnaissance vocale. Le dépôt mentionné à la fin de ce billet contient un nœud LLM personnalisé pour Elastic Agent Builder, compatible avec LiveKit. Il vous suffit de remplacer <code>AGENT_ID</code> par le vôtre et de le connecter à votre instance Kibana.</p><h2>Premiers pas</h2><p>Consultez le code et testez-le vous-même <a href="https://github.com/KDKHD/elastic_agent_builder_livekit">ici</a>. </p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Agent Builder, bien plus qu’une interface de discussion : vers une infrastructure augmentée]]></title>
            <description><![CDATA[Découvrez Elastic Agent Builder avec l’infrastructure augmentée (Augmented Infrastructure), un agent d’IA qui permet des opérations, du développement et des tests Synthetic augmentés.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/agent-builder-augmented-infrastructure</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/agent-builder-augmented-infrastructure</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Wert, Bill Easton, Gil Raphaelli, Graham Hudgins, Norrie Taylor]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e08daf78f328e826f39d06329f6a5487f75d178d-1272x700.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Ce n&#x27;est pas une discussion. Nous le faisons.</strong></p><p>Nous avons tous été témoins de l’essor des agents d’IA. Ils se révèlent particulièrement performants pour le résumé de textes, l’écriture de scripts et l’extraction de réponses issues de bases documentaires. Pourtant, dans les domaines du DevOps et de la fiabilité système (SRE), nous faisions face à un obstacle particulièrement frustrant. L’immense majorité des agents reste confinée au modèle du support client ; s’ils analysent et échangent, ils demeurent incapables d’intervenir directement sur les couches d’infrastructure dont ils ont la charge.</p><p>Lors de notre récent hackathon, nous avons pris le parti de briser définitivement cette contrainte.</p><p>Nous avons conçu l’<strong>Infrastructure augmentée</strong> : un copilote d’infrastructure qui ne se contente pas de vous conseiller, mais qui crée, déploie, surveille et répare également votre environnement de production.</p><h2><strong>Le problème : copier, reformater, coller</strong></h2><p>Les agents standards fonctionnent en vase clos. Face à une panne majeure représentant une perte de 5 millions de dollars, l’assistance d’un agent standard se limite à la simple lecture du protocole de remise en service. Mais c’est toujours à <em>vous</em> d’effectuer le travail. Il vous reste encore à extraire le code, à en assurer la compatibilité avec votre environnement, puis à procéder manuellement à sa saisie dans la console.</p><p>Nous recherchions un agent capable de faire la part des choses entre le <em>discours</em> sur Kubernetes et l’<em>exécution technique</em> sur Kubernetes.</p><h2><strong>Au cœur du système : présentation d’Elastic Agent Builder.</strong></h2><p>Pour concevoir cette solution, nous ne sommes pas partis d’une page blanche. Nous l’avons conçue sur la base d’<a href="https://www.elastic.co/elasticsearch/agent-builder"><strong>Elastic Agent Builder</strong></a>. À titre de rappel, Elastic Agent Builder est une architecture logicielle dédiée au développement rapide d’agents, agissant comme interface entre un modèle de langage (LLM), tel que Google Gemini, et les données propriétaires hébergées dans Elasticsearch.</p><p>Agent Builder peut être utilisé pour l’IA conversationnelle en l’ancrant sur des données internes, comme des documents ou des logs. Mais sa fonctionnalité la plus puissante est la possibilité d’assigner des <strong>outils</strong>. Ces outils permettent au LLM de sortir de l’interface de discussion pour accomplir des tâches spécifiques. Nous avons compris qu’en exploitant tout le potentiel de cette fonction, l’Agent Builder pourrait devenir un véritable pilier de l’automatisation.</p><h2><strong>Le faire fonctionner : création de la première version</strong></h2><p>Dès le début du projet, notre ambition était de permettre aux agents d’exercer une action concrète sur leur environnement externe. Une idée a germé : pourquoi ne pas créer un « runner », un logiciel chargé d’exécuter sur la machine hôte toutes les instructions générées par l’agent ? Puis, nous avons envisagé ceci : et si les « runners », Elastic Agent Builder et l’utilisateur communiquaient en temps réel, comme lors d’une conférence téléphonique ?</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8dc8317c1301b8eb7b89438529e8d8d17411c95a-1024x559.png" style="width: 100%;" /><p>Nous avons commencé par concevoir un projet Python, Augmented Infrastructure Runners, qui consistait essentiellement en une boucle <code>while(true)</code> interrogeant chaque seconde l’API des conversations d’Elastic Agent Builder pour y détecter une syntaxe spécifique que nous avions créée :</p><pre><code>{
	"tool_name": "my_tool",
       "tool_arguments": "\{stringified json arguments\}"
}</code></pre><p>Nous avons ensuite mis à jour l&#x27;invite pour l&#x27;enseigner sur notre nouvelle syntaxe d&#x27;appel d&#x27;outil. Bill contribue à la maintenance de <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a>, la solution de référence en Python pour le développement de serveurs conformes au Model Context Protocol (MCP). Il a entrepris d’utiliser le client FastMCP conjointement avec ce nouveau runner afin de coupler les serveurs MCP et d’exposer leurs outils au sein de l’environnement d’exécution. Lorsque l’agent voyait cela, il exécutait l’appel de l’outil et <code>POST</code> les résultats dans la conversation, comme si l’utilisateur lui-même les avait envoyés. Cela incitait le LLM à répondre au résultat, et c’est ainsi que tout a commencé !</p><p>C&#x27;était génial mais cela posait deux problèmes principaux :</p><ol><li>L’agent se contenterait de projeter l’intégralité des données JSON au beau milieu de l’échange avec l’utilisateur.</li><li>Dans l’API des conversations, les messages n’étaient accessibles qu’une fois le tour de parole terminé, soit après l’émission de la réponse par le LLM.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7b3755aeae17722ff1bb9677712293e9195f96a0-1058x1034.png" style="width: 100%;" /><p>Nous nous sommes alors attachés à trouver comment déporter cette tâche en tâche de fond.</p><p>Nous avons ensuite donné à l&#x27;agent un outil appelé <code>call_external_tool</code> avec deux arguments : le <code>tool_name</code> et les arguments JSON sous forme de chaîne. Bien que cet appel d’outil externe ne produise aucun retour, il resterait néanmoins détectable au sein de la requête <code>GET</code> envoyée à l’API des conversations. L’étape suivante a consisté à donner aux runners la permission d’alimenter Elasticsearch en documents, que l’agent d’Elastic Agent Builder pouvait alors consulter à la demande. L&#x27;agent fonctionne toujours en réponse à un message d&#x27;utilisateur, nous devons donc démarrer l&#x27;agent avec un message d&#x27;utilisateur afin qu&#x27;il recherche des résultats et poursuive le traitement. Nous avons donc demandé aux agents d’insérer un petit message dans le chat pour reprendre la conversation :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/61ff59a57c68ed5fad492d19c0580644113a507d-1600x1321.png" style="width: 100%;" /><p>Ainsi, nous avions désormais des appels d&#x27;outils externes. Cependant, en raison du deuxième problème mentionné ci-dessus, nous avons dû supprimer cette dernière partie de démarrage. Sinon, chaque appel à un outil externe nécessitait un cycle complet de conversation pour récupérer les résultats !</p><h2><strong>Pour aller plus loin : présentation des workflows</strong></h2><p>En plus des appels d’outils via ES|QL et la recherche d’index, les agents d’Agent Builder peuvent solliciter des outils Elastic basés sur des workflows. Les workflows Elastic offrent une méthode flexible et simple à gérer pour exécuter une séquence arbitraire d’actions et de logiques. Dans notre cas, le rôle du workflow se limite à l’enregistrement d’une demande d’outil externe dans Elasticsearch et à la transmission d’un identifiant (ID) pour le suivi des résultats. Le résultat est une définition de workflow simple, articulée comme suit :</p><pre><code>name: ai-tool-call
enabled: true
triggers:
  - type: manual
inputs:
  - name: runner_id
    type: string
  - name: tool_calls
    type: string

steps:
  - name: store_request
    type: elasticsearch.create
    with:
      index: distributed-tool-requests
      id: "{{inputs.runner_id}}_{{ execution.id }}"
      document:
        request_id: "{{ execution.id }}"
        runner_id: "{{inputs.runner_id}}"
        tool_call: "{{inputs.tool_calls}}"
        status: "unhandled"

  - name: output_result
    type: console
    with:
      message: "Called tool, with execution id: {{ execution.id }}. Use this ID to poll the results."</code></pre><p>Ainsi, au lieu de compter sur l’écriture de la requête d’appel d’outil dans la conversation, les runners peuvent simplement interroger l’index <code>distributed-tool-requests</code> d’Elasticsearch pour de nouvelles requêtes d’outils externes et faire un rapport des résultats dans un autre index Elasticsearch avec le <code>execution.id</code> fourni.</p><p>Cela élimine les deux principaux problèmes mentionnés ci-dessus :</p><ol><li>L’historique de la conversation n’est plus encombré par les données de transfert des appels d’outils externes.</li><li>Comme les runners interrogent l’index Elasticsearch au lieu de l’historique de conversation, ils ne sont plus bloqués par l’achèvement du cycle d’échange pour que les requêtes d’outils externes deviennent visibles.</li></ol><p>L’intérêt principal de ce deuxième point est que l’exécution des requêtes vers les outils externes débute pendant que l’agent « réfléchit », sans attendre que le tour de parole soit terminé. Cela nous permet d’instruire le LLM, via le prompt système, d’interroger les résultats de l’outil externe jusqu’à ce qu’ils soient disponibles, éliminant ainsi le besoin d’un message de relance. Dans l’ensemble, cette approche fluidifie l’interaction : le LLM est désormais capable de gérer simultanément plusieurs appels d’outils externes lors d’une seule itération. Cela lui permet de traiter des requêtes utilisateur complexes de manière groupée, plutôt que de fragmenter le processus.</p><h2><strong>Mise en commun</strong></h2><p>Pour combler le fossé entre le LLM et la baie de serveurs, nous avons développé une architecture spécifique en exploitant les fonctionnalités des outils d&#x27;Agent Builder :</p><ol><li><strong>Les runners de l&#x27;infrastructure augmentée :</strong> Nous avons déployé des runners légers à l&#x27;intérieur des environnements cibles (serveurs, clusters Kubernetes, comptes cloud). Ces exécuteurs sont connectés directement à Elastic, en utilisant des endpoints sécurisés et des secrets accessibles uniquement à chacun des runners.</li><li><strong>Recherche ES|QL :</strong> Le copilote utilise <strong>ES|QL</strong> d&#x27;Elastic pour effectuer des recherches hybrides. Il ne se contente pas de rechercher des connaissances ; il recherche des <em>capacités</em>. Il interroge les exécuteurs connectés pour voir quels outils sont disponibles (par exemple, <code>list_ec2_instances</code>, <code>install_helm_chart</code>).</li><li><strong>Exécution du workflow :</strong> Une fois que l&#x27;agent a décidé d&#x27;un plan d&#x27;action, il crée un workflow structuré.</li><li><strong>Boucle de rétroaction :</strong> Les runners exécutent la commande localement et rapportent les résultats dans Elasticsearch. Le copilote lit le résultat de l’index et décide de l’étape suivante.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/76be256da722c1965971fc506502768bd890f0c4-1290x1076.png" style="width: 100%;" /><h2><strong>Démonstration : transformer un incident critique en levier d’observabilité</strong></h2><a href="https://videos.elastic.co/watch/bRgPJfJLeABjPMMhvy1sbB">Watch video on Vidyard</a><p>Dans la vidéo, nous avons présenté deux scénarios distincts démontrant la puissance de cette architecture.</p><h3><strong>Scénario 1 : DevOps à la rescousse</strong></h3><p>Le point de départ est un incident critique : un utilisateur confronté à une perte de 5 millions de dollars due à un défaut de visibilité dans son cluster Kubernetes.</p><ul><li><strong>La demande :</strong> « Comment m&#x27;assurer que cela ne se reproduise plus ? »</li><li><strong>Action :</strong> L’agent ne s’est pas contenté de fournir un tutoriel. Il a identifié le cluster, créé les espaces de nom nécessaires, généré des secrets Kubernetes, installé l’opérateur OpenTelemetry et a immédiatement fourni un lien vers un tableau de bord APM en direct.</li><li><strong>Le résultat :</strong> Une observabilité complète de Kubernetes et des informations sur les applications sans que l&#x27;utilisateur n&#x27;écrive une seule ligne de YAML.</li></ul><h3><strong>Scénario 2 : Transfert de Security</strong></h3><p>En sécurité des infrastructures, un principe de base prévaut : l’impossibilité de protéger ce qui échappe à notre visibilité. En pleine intervention de secours DevOps, l’agent identifie une occasion d’optimiser la sécurité globale de l’infrastructure.</p><p>En s’appuyant sur une alerte générée lors d’une analyse Elastic Observability, nous illustrons la capacité d’un analyste sécurité à interagir en langage naturel avec son infrastructure. L’objectif est double : recenser précisément les ressources cloud existantes et mettre en œuvre les solutions de protection indispensables.</p><ul><li><strong>Découverte :</strong> Le copilote a énuméré les ressources AWS pour le spécialiste de la sécurité et a identifié une lacune critique : une instance Amazon Elastic Compute Cloud (EC2) et un cluster Amazon Elastic Kubernetes Service (EKS) avec des points de terminaison publics dépourvus de protection des points de terminaison.</li><li><strong>Remédiation :</strong> Avec une simple approbation, le copilote a déployé <strong>Elastic Security</strong> <strong>détection et réponse étendues (XDR) et détection et réponse cloud (CDR)</strong> sur les ressources vulnérables, assurant la sécurité de l&#x27;environnement en temps réel.</li><li><strong>Le résultat :</strong> Protection des ressources AWS déployées avec une sécurité totale à l’exécution.</li></ul><h2><strong>Perspectives : vers une infrastructure intégralement augmentée</strong></h2><p>Ce projet démontre la capacité d’Elastic Agent Builder à agir comme le centre de pilotage de vos opérations distribuées. Notre champ d’action dépasse désormais le cadre strict de l’infrastructure. Les capacités de notre technologie de runner s’étendent à :</p><ul><li><strong>Synthetics augmenté :</strong> Diagnostiquer les erreurs TLS chez les runners du monde entier.</li><li><strong>Développement augmenté :</strong> Création de requêtes d&#x27;extraction et implémentation de CAPTCHA sur les services frontend.</li><li><strong>Opérations augmentées :</strong> reconfiguration automatique des résolveurs DNS en cas de panne.</li></ul><h2><strong>Essayez par vous-même</strong></h2><p>Nous pensons que l&#x27;avenir de l&#x27;IA ne se limite pas à l&#x27;assistance par chat, mais aussi à une <strong>infrastructure augmentée</strong>. Il s&#x27;agit d&#x27;avoir un partenaire qui peut déployer, réparer, observer et protéger à vos côtés.</p><p>Consultez le code et essayez-le par vous-même avec les runners distribués (<a href="https://github.com/strawgate/augmented-infrastructure">GitHub</a>) et Elastic Agent Builder sur <a href="https://cloud.elastic.co/">Elastic Cloud Serverless</a> dès aujourd&#x27;hui !</p><ul><li>Créez un projet sans serveur sur Elastic Cloud.</li><li>Déployer le code vers un runner.</li><li>Configurer le runner.</li><li>Configurer votre fichier mcp.json.</li><li>Démarrer l&#x27;agent, qui créera automatiquement votre agent et ses outils.</li><li>Dialoguez avec un agent capable de raisonner, de planifier et d’exécuter des actions sur vos runners distribués !</li></ul><p><strong>L’équipe : </strong><em>Alex, Bill, Gil, Graham et Norrie</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Statistiques ES|QL plus rapides avec des tables de hachage de style suisse]]></title>
            <description><![CDATA[Comment le hachage d'inspiration suisse et la conception compatible SIMD permettent d'obtenir des accélérations constantes et mesurables dans le langage de requête Elasticsearch Query Language (ES|QL).]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/esql-swiss-hash-stats</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/esql-swiss-hash-stats</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Hegarty, Matthew Alp, Nik Everet]]></dc:creator>
            <pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/21036e031070f14faccb2b53b22723de2750c391-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Nous avons récemment remplacé des éléments clés de l&#x27;implémentation des tables de hachage d&#x27;Elasticsearch par une conception de type suisse et constaté des temps de construction et d&#x27;itération jusqu&#x27;à 2 à 3 fois plus rapides sur des charges de travail uniformes à forte cardinalité. Il en résulte une latence réduite, un meilleur débit et des performances plus prévisibles pour les opérations statistiques et analytiques du langage de requête Elasticsearch (ES|QL).</p><h2>Pourquoi c&#x27;est important</h2><p>La plupart des workflows analytiques classiques se résument finalement à regrouper des données. Qu&#x27;il s&#x27;agisse de calculer la consommation moyenne de données par hôte, de compter les événements par utilisateur ou d&#x27;agréger des indicateurs selon différentes dimensions, l&#x27;opération de base reste la même : associer des clés à des groupes et mettre à jour les agrégats en cours.</p><p>À petite échelle, presque n&#x27;importe quelle table de hachage convenable fonctionne bien. À grande échelle (des centaines de millions de documents et des millions de groupes distincts), les détails commencent à avoir leur importance. Les facteurs de charge, la stratégie de sondage, l&#x27;organisation de la mémoire et le comportement du cache peuvent faire la différence entre des performances linéaires et une avalanche d&#x27;erreurs de cache.</p><p>Elasticsearch prend en charge ces charges de travail depuis des années, mais nous cherchons constamment à moderniser ses algorithmes de base. C&#x27;est pourquoi nous avons évalué une nouvelle approche inspirée des tables suisses et l&#x27;avons appliquée au calcul des statistiques par ES|QL.</p><h2>Que sont exactement les tables suisses ?</h2><p>Les tables suisses sont une famille de tables de hachage modernes popularisées par SwissTable de Google, puis adoptées par Abseil et d&#x27;autres bibliothèques.</p><p>Les tables de hachage traditionnelles passent beaucoup de temps à rechercher des pointeurs ou à charger des clés pour finalement constater qu&#x27;elles ne correspondent pas. La caractéristique principale des tables suisses est leur capacité à rejeter la plupart des requêtes grâce à une structure de tableau en cache de petite taille, stockée séparément des clés et des valeurs et appelée <em>octets de contrôle</em>, ce qui réduit considérablement le trafic mémoire.</p><p>Chaque octet de contrôle représente un emplacement unique et, dans notre cas, encode deux éléments : si l&#x27;emplacement est vide et une courte empreinte numérique dérivée du hachage. Ces octets de contrôle sont disposés de manière contiguë en mémoire, généralement par groupes de 16, idéal pour le traitement SIMD (<a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">Single Instruction, Multiple Data</a>).</p><p>Au lieu de sonder un emplacement à la fois, les tables suisses parcourent un bloc entier d&#x27;octets de contrôle à l&#x27;aide d&#x27;instructions vectorielles. En une seule opération, le processeur compare l&#x27;empreinte de la clé entrante à 16 emplacements et élimine les entrées vides. Seuls les candidats retenus après ce parcours rapide nécessitent le chargement et la comparaison des clés réelles.</p><p>Cette conception privilégie une meilleure localité du cache et réduit considérablement les chargements aléatoires, au détriment d&#x27;une petite quantité de métadonnées supplémentaires. À mesure que la table s&#x27;agrandit et que les chaînes de détection s&#x27;allongent, ces avantages deviennent de plus en plus précieux.</p><h2>SIMD au centre</h2><p>La vraie star de la série est l&#x27;architecture SIMD.</p><p>Les octets de contrôle sont non seulement compacts, mais aussi conçus spécifiquement pour être traités par des instructions vectorielles. Une seule comparaison SIMD peut vérifier simultanément 16 empreintes, transformant ainsi une boucle classique en quelques opérations étendues. Exemple :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bd418778f0c6144f8f5f18419f6220ac0c935c7a-903x407.png" style="width: 100%;" /><p>En pratique, cela signifie :</p><ul><li>Moins de branches.</li><li>Des chaînes de détection plus courtes.</li><li>Moins de chargements depuis la mémoire des clés et des valeurs.</li><li>Une bien meilleure utilisation des unités d&#x27;exécution du processeur.</li></ul><p>La plupart des recherches ne dépassent jamais l&#x27;étape de l&#x27;analyse de l&#x27;octet de contrôle. Lorsqu&#x27;elles y parviennent, le travail restant est ciblé et prévisible. C&#x27;est précisément le type de charge de travail pour lequel les processeurs modernes excellent.</p><h2>SIMD sous le capot</h2><p>Pour les lecteurs qui aiment jeter un œil sous le capot, voici ce qui se passe lors de l&#x27;insertion d&#x27;une nouvelle clé dans la table. Nous utilisons l&#x27;API Panama Vector avec des vecteurs de 128 bits, opérant ainsi sur 16 octets de contrôle en parallèle.</p><p>L&#x27;extrait suivant montre le code généré sur un processeur Intel Rocket Lake avec AVX-512. Bien que les instructions reflètent cet environnement, la conception ne dépend pas d&#x27;AVX-512. Les mêmes opérations vectorielles de haut niveau sont émises sur d&#x27;autres plateformes en utilisant des instructions équivalentes (par exemple, AVX2, SSE ou NEON).</p><pre><code>; Load 16 control bytes from the control block
vmovdqu xmm0, XMMWORD PTR [r9+r10*1+0x10]

; Broadcast the 7-bit fingerprint of the new key across the vector
vpbroadcastb xmm1, r11d

; Compare all 16 control bytes to the new fingerprint
vpcmpeqb k7, xmm0, xmm1
kmovq rbx, k7

; Check if any matches were found
test rbx, rbx
jne <handle_match></code></pre><p>Chaque instruction a un rôle clair dans le processus d&#x27;insertion :</p><ul><li><code>vmovdqu</code>: Charge 16 octets de contrôle consécutifs dans le registre <code>xmm0</code> de 128 bits.</li><li><code>vpbroadcastb</code>: Réplique l&#x27;empreinte 7 bits de la nouvelle clé sur toutes les voies du registre <code>xmm1</code>.</li><li><code>vpcmpeqb</code>: Compare chaque octet de contrôle à l&#x27;empreinte diffusée, produisant un masque de correspondances potentielles.</li><li><code>kmovq</code> + <code>test</code> : Déplace le masque vers un registre à usage général et vérifie rapidement si une correspondance existe.</li></ul><p>Enfin, nous avons opté pour le sondage de groupes de 16 octets de contrôle à la fois, car les tests comparatifs ont montré que l&#x27;extension à 32 ou 64 octets avec des registres plus larges n&#x27;apportait aucun avantage mesurable en termes de performances.</p><h2>Intégration dans ES|QL</h2><p>L&#x27;adoption du hachage suisse dans Elasticsearch n&#x27;a pas été une simple solution de remplacement. ES|QL impose des exigences strictes de gestion de la mémoire, de sécurité et d&#x27;intégration au reste du moteur de calcul.</p><p>Nous avons étroitement intégré la nouvelle table de hachage à la gestion de la mémoire d&#x27;Elasticsearch, notamment au recycleur de pages et à la comptabilisation des coupures, afin de garantir la visibilité et la limitation des allocations. Les agrégations d&#x27;Elasticsearch sont stockées de manière dense et indexées par un identifiant de groupe, ce qui optimise la structure de la mémoire et la rapidité d&#x27;itération, tout en autorisant certaines optimisations de performance grâce à l&#x27;accès aléatoire.</p><p>Pour les clés binaires de longueur variable, nous mettons en cache le hachage complet ainsi que l&#x27;identifiant du groupe. Cela évite le recalcul coûteux des codes de hachage lors du sondage et améliore la localité du cache en regroupant les métadonnées associées. Lors du rehachage, nous pouvons nous appuyer sur le hachage et les octets de contrôle mis en cache sans avoir à examiner les valeurs elles-mêmes, ce qui réduit les coûts de redimensionnement.</p><p>Une simplification importante de notre implémentation est que les entrées ne sont jamais supprimées. Cela élimine le besoin de <em>marqueurs</em> (pour identifier les emplacements précédemment occupés) et permet aux emplacements vides de rester véritablement vides, ce qui améliore encore le comportement de sondage et maintient l&#x27;efficacité des analyses d&#x27;octets de contrôle.</p><p>Le résultat est une conception qui s&#x27;intègre naturellement dans le modèle d&#x27;exécution d&#x27;Elasticsearch tout en préservant les caractéristiques de performance qui rendent les tables suisses attrayantes.</p><h2>Quelles sont ses performances ?</h2><p>Pour les petites cardinalités, les tables suisses offrent des performances globalement équivalentes à celles de l&#x27;implémentation existante. Ce résultat est normal : lorsque les tables sont petites, les effets de cache sont moins importants et il y a peu de détections à optimiser.</p><p>À mesure que la cardinalité augmente, la situation change rapidement.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/24900afc47ab07b0e9933f6117b99d0f4613f794-962x599.png" style="width: 100%;" /><p>La carte thermique ci-dessus montre les facteurs d&#x27;amélioration du temps de traitement pour différentes tailles de clés (8, 32, 64 et 128 octets) pour des cardinalités allant de 1 000 à 10 000 000 de groupes. À mesure que la cardinalité augmente, le facteur d&#x27;amélioration s&#x27;accroît régulièrement, jusqu&#x27;à 2 à 3 fois pour les distributions uniformes.</p><p>Cette tendance correspond exactement aux prévisions de conception. Une cardinalité plus élevée entraîne des chaînes de détection plus longues dans les tables de hachage traditionnelles, tandis que le mode de sondage de type suisse continue de résoudre la plupart des recherches dans des blocs d&#x27;octets de contrôle compatibles SIMD.</p><h2>Le comportement du cache raconte l&#x27;histoire</h2><p>Pour mieux comprendre les gains de vitesse, nous avons exécuté le même JMH <a href="https://github.com/elastic/elasticsearch/pull/139343/files#diff-d0e0cc91a7495bf36b2d44eacce95f5185d01879e5f6c38089ac7a89aad17da7"><code>benchmarks</code></a> sous Linux <code>perf</code> et capturé les statistiques du cache et du TLB.</p><p>Comparée à l&#x27;implémentation originale, la version suisse effectue environ 60 % d&#x27;accès au cache en moins. Les chargements du cache de dernier niveau sont 4 fois moins nombreux, et les défauts de chargement du cache LLC 6 fois moins. Étant donné que ces défauts se traduisent souvent directement par des accès à la mémoire principale, cette réduction explique à elle seule une grande partie de l&#x27;amélioration globale des performances.</p><p>Plus on se rapproche du processeur, moins on observe d&#x27;échecs de cache de données L1 et près de 6 fois moins d&#x27;échecs de TLB de données, ce qui indique une localité spatiale plus étroite et des schémas d&#x27;accès à la mémoire plus prévisibles.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6e49b7609fba83e33692cb9834552b6ca7e42a83-998x499.png" style="width: 100%;" /><p>C&#x27;est l&#x27;avantage concret des octets de contrôle compatibles SIMD. Au lieu de charger sans cesse des clés et des valeurs depuis des emplacements mémoire dispersés, la plupart des requêtes sont résolues par l&#x27;analyse d&#x27;une structure compacte résidant dans le cache. Moins de mémoire utilisée signifie moins d&#x27;échecs de lecture, et moins d&#x27;échecs de lecture signifient des requêtes plus rapides.</p><h2>Conclusion</h2><p>En adoptant une conception de table de hachage de style suisse et en misant fortement sur le sondage compatible SIMD, nous avons obtenu des gains de vitesse de 2 à 3 fois pour les charges de travail statistiques ES|QL à cardinalité élevée, ainsi que des performances plus stables et prévisibles.</p><p>Ce travail met en lumière comment les structures de données modernes optimisées pour le processeur peuvent générer des gains substantiels, même pour des problèmes complexes comme les tables de hachage. Il reste encore beaucoup à explorer, notamment en matière de spécialisation des types primitifs et d&#x27;utilisation dans d&#x27;autres opérations à forte cardinalité, telles que les jointures. Ces pistes s&#x27;inscrivent dans le cadre d&#x27;un effort plus vaste et continu de modernisation du fonctionnement interne d&#x27;Elasticsearch.</p><p>Si vous êtes intéressé par les détails ou souhaitez suivre le travail, consultez cette <a href="https://github.com/elastic/elasticsearch/pull/139343">pull request</a> et la <a href="https://github.com/elastic/elasticsearch/issues/138799">meta issue</a> qui suit les progrès sur Github.</p><p>Bon hachage !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Gestion de la mémoire agentique avec Elasticsearch.]]></title>
            <description><![CDATA[Création d'agents plus sensibles au contexte et plus efficaces grâce à la gestion des mémoires avec Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/agentic-memory-management-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/agentic-memory-management-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Someshwaran Mohankumar]]></dc:creator>
            <pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/80907fd072e72d6ec902470b449c9f337957a0d7-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans la discipline émergente de l&#x27;<strong>ingénierie contextuelle</strong>, il est crucial de fournir aux agents d&#x27;IA les bonnes informations au bon moment. L&#x27;un des aspects les plus importants de l&#x27;ingénierie contextuelle est la gestion de la <strong>mémoire</strong> d&#x27;une IA. Tout comme les humains, les systèmes d&#x27;IA s&#x27;appuient à la fois sur une mémoire à court terme et sur une mémoire à long terme pour se souvenir des informations. Si nous voulons que les agents à grand modèle de langage (LLM) poursuivent des conversations logiques, se souviennent des préférences des utilisateurs ou s&#x27;appuient sur des résultats ou des réponses antérieurs, nous devons les doter de mécanismes de mémoire efficaces.</p><p>Après tout, tout ce qui figure dans le contexte influence les réponses de l’IA. Le principe de l&#x27;<em>entrée et de la sortie</em> des déchets est vrai.</p><p>Dans cet article, nous allons présenter ce que signifie la mémoire à court et à long terme pour les agents IA, en particulier :</p><ul><li>La différence entre la mémoire à court terme et la mémoire à long terme.</li><li>Comment elles se rapportent aux techniques de génération augmentée de récupération (RAG) avec des bases vectorielles, comme Elasticsearch, et pourquoi une gestion attentive de la mémoire est nécessaire.</li><li>Les risques liés à la négligence de la mémoire, y compris le débordement de contexte et l&#x27;empoisonnement contextuel.</li><li>Les bonnes pratiques, telles que l&#x27;élagage du contexte, la synthèse et la récupération uniquement des informations pertinentes, permettent de maintenir la mémoire de l&#x27;agent à la fois utile et sécurisée.</li><li>Enfin, nous examinerons comment la mémoire peut être partagée et propagée dans les systèmes multi-agents pour permettre une collaboration sans confusion grâce à Elasticsearch.</li></ul><h2>Mémoire à court terme versus mémoire à long terme dans les agents d’IA</h2><p><strong><em>La mémoire à court terme</em></strong> dans un agent IA fait généralement référence au contexte ou à l’état immédiat de la conversation — essentiellement, l’historique de discussion actuel ou les messages récents dans la session active. Cela inclut la dernière requête de l’utilisateur et les échanges récents d’allers-retours. C’est très similaire aux informations qu’une personne garde en tête lors d’une conversation en cours.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4fbcc6f68055b2bccefc4176297a4ca50056dc0d-764x498.png" style="width: 100%;" /><p>Les frameworks d&#x27;IA maintiennent souvent cette mémoire transitoire dans l&#x27;état de l&#x27;agent (par exemple, en utilisant un pointeur de contrôle pour stocker l&#x27;état de la conversation, comme le montre <a href="https://docs.langchain.com/oss/python/langgraph/persistence#checkpoints">cet exemple de LangGraph</a>). La mémoire à court terme est <strong><em>limitée à une session</em></strong>, c&#x27;est-à-dire qu&#x27;elle existe dans le cadre d&#x27;une conversation ou d&#x27;une tâche unique et qu&#x27;elle est réinitialisée ou effacée à la fin de cette session, à moins qu&#x27;elle ne soit explicitement sauvegardée ailleurs. Un exemple de mémoire à court terme liée à une session serait le <strong><a href="https://help.openai.com/en/articles/8914046-temporary-chat-faq">chat temporaire</a> </strong>disponible dans ChatGPT.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/150bdf209cda5ed20b59cddf34e624ad1a8016aa-1100x577.png" style="width: 100%;" /><p><strong><em>La mémoire à long terme</em></strong>, en revanche, désigne les informations qui persistent <strong>au fil des conversations ou des sessions</strong>. Il s&#x27;agit des connaissances qu&#x27;un agent conserve au fil du temps, des faits qu&#x27;il a appris précédemment, des préférences de l&#x27;utilisateur ou de toute autre donnée que nous lui avons demandé de garder en mémoire de manière permanente.</p><p>La mémoire à long terme est généralement mise en œuvre en stockant et en récupérant les données à partir d&#x27;une source externe, telle qu&#x27;un fichier ou une base vectorielle située en dehors de la fenêtre contextuelle immédiate. Contrairement à l&#x27;historique de discussion à court terme, la mémoire à long terme n’est pas automatiquement incluse dans chaque requête. Au lieu de cela, sur la base d&#x27;un scénario donné, l&#x27;agent doit le <strong>rappeler</strong> ou le retrouver lorsque les outils pertinents sont invoqués. En pratique, la mémoire à long terme peut inclure les informations de profil de l’utilisateur, des réponses ou analyses antérieures produites par l’agent, ou une base de connaissances que l’agent peut consulter.</p><p>Par exemple, si vous avez un agent planificateur de voyage, la <em>mémoire à court terme</em> contiendrait les détails de la demande de voyage actuelle (dates, destination, budget) et toutes les questions de suivi dans cette conversation ; tandis que la <em>mémoire à long terme</em> pourrait stocker les préférences générales de voyage de l&#x27;utilisateur, les itinéraires passés et d&#x27;autres faits partagés lors de sessions précédentes. Lorsque l&#x27;utilisateur revient plus tard, l&#x27;agent peut puiser dans cette base de données à long terme (par exemple, l&#x27;utilisateur aime les plages et les montagnes, dispose d&#x27;un budget moyen de 100 000 INR, a une liste de lieux à visiter et préfère découvrir l&#x27;histoire et la culture plutôt que les attractions pour enfants) afin de ne pas traiter l&#x27;utilisateur comme une page blanche à chaque fois.</p><p>La mémoire à court terme (historique des conversations) fournit un contexte immédiat et une continuité, tandis que la mémoire à long terme fournit un contexte plus large dans lequel l&#x27;agent peut puiser en cas de besoin. Les cadres d&#x27;agents d&#x27;IA les plus avancés permettent les deux : ils gardent la trace des dialogues récents pour maintenir le contexte <em>et</em> proposent des mécanismes pour rechercher ou stocker des informations dans un référentiel à plus long terme. La gestion de la mémoire à court terme garantit qu&#x27;elle reste dans la fenêtre de contexte, tandis que la gestion de la mémoire à long terme aide l&#x27;agent à ancrer les réponses sur la base d&#x27;interactions et de personas antérieures.</p><h2>Mémoire et RAG en ingénierie contextuelle</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/46635aa11ceff89b8d6a26ac3e22da52407d82f3-1600x900.png" style="width: 100%;" /><p><strong><em>Comment pouvons-nous donner à un agent IA une mémoire à long terme utile en pratique ?</em></strong></p><p>La <strong><em>mémoire sémantique</em></strong>, souvent mise en œuvre par le biais de la <strong>retrieval-augmented generation (RAG),</strong> constitue l&#x27;une des principales approches pour la mémoire à long terme. Cela consiste à couler le LLM avec un stockage de connaissances externe ou un datastore vectoriel, comme Elasticsearch. Lorsque le LLM a besoin d&#x27;informations au-delà de ce qui est contenu dans l&#x27;invite ou dans son entraînement intégré, il effectue une récupération sémantique contre Elasticsearch et injecte les résultats les plus pertinents dans l&#x27;invite en tant que contexte. Ainsi, le contexte effectif du modèle inclut non seulement la conversation récente (mémoire à court terme), mais aussi des faits pertinents à long terme récupérés à la volée. Le LLM fonde ensuite sa réponse sur son propre raisonnement et les informations récupérées, combinant efficacement la mémoire à court terme et la mémoire à long terme pour produire une réponse plus précise et contextuelle.</p><p><strong>Elasticsearch </strong>peut être utilisé pour mettre en place une mémoire à long terme pour les agents d’IA. Voici un exemple de haut niveau montrant comment le contexte peut être récupéré depuis Elasticsearch pour la mémoire à long terme.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/41ccbc7b5171e8170ac300139a963c0708816ba6-1600x900.png" style="width: 100%;" /><p>De cette façon, l’agent « se souvient » en recherchant des données pertinentes plutôt que de tout stocker dans son invite limitée, <strong>ce qui entraîne différents risques.</strong></p><p><strong>L&#x27;utilisation de RAG avec Elasticsearch ou tout stockage de vecteurs offre de multiples avantages :</strong></p><p>Premièrement, il <strong>étend la connaissance du</strong> modèle au-delà de son seuil d&#x27;apprentissage. L’agent peut récupérer des informations à jour ou des données spécifiques au domaine que le LLM pourrait ne pas connaître. Ceci est crucial pour les questions concernant des événements récents ou des sujets spécialisés.</p><p>Deuxièmement, récupérer le contexte à la demande aide à réduire les hallucinations, surtout que les LLM ne sont pas entraînés sur des données propriétaires ou très spécialisées par rapport à votre cas d&#x27;utilisation spécifique, ce qui est très susceptible de les exposer à des hallucinations. Au lieu que le LLM devine ou invente de nouvelles informations comme il a été incité par évaluation, comme l’a souligné un article récent d’OpenAI (<a href="https://arxiv.org/pdf/2509.04664">Why Language Models Hallucinate</a>), le modèle peut être fondé sur des références factuelles provenant d’Elasticsearch. Bien entendu, le LLM dépend de la fiabilité des données du référentiel vectoriel pour prévenir véritablement la désinformation, et les données pertinentes sont extraites conformément aux mesures de pertinence fondamentales.</p><p>Troisièmement, RAG permet à un agent de travailler avec des bases de connaissances bien plus vastes que tout ce que vous pourriez inclure dans une invite. Au lieu d&#x27;insérer des documents entiers, comme de longs documents de recherche ou des documents politiques, dans la fenêtre contextuelle et de risquer une surcharge ou un <a href="https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch#context-poisoning">empoisonnement du contexte</a> du raisonnement du modèle par une information non pertinente, RAG s&#x27;appuie sur le <a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">découpage</a>. Les documents volumineux sont divisés en morceaux plus petits et sémantiquement significatifs, et le système ne récupère que les quelques segments les plus pertinents pour la requête. Ainsi, le modèle n&#x27;a pas besoin d&#x27;un contexte d&#x27;un million de mots pour paraître bien informé ; il lui suffit d&#x27;avoir accès aux bons morceaux d&#x27;un corpus beaucoup plus vaste.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6897356c9f0940e35a63d005e9cd20bc33e5dd7-1600x931.png" style="width: 100%;" /><p>Il est important de noter qu’à mesure que les fenêtres contextuelles des LLM se sont agrandies (<a href="https://www.anthropic.com/news/1m-context">certains modèles supportent désormais des centaines de milliers, voire des millions de jetons</a><em>),</em> un débat a surgi sur la question de savoir si RAG est « mort ». Pourquoi ne pas intégrer toutes les données dans l&#x27;invite ? Si vous êtes du même avis, reportez-vous à cet excellent article de mes collègues Jeffrey Rengifo et Eduard Martin, <a href="https://www.elastic.co/search-labs/blog/rag-vs-long-context-model-llm">Longer context ≠ better : Why RAG still matters</a>. Cela évite le problème du « déchets en entrée, déchets en sortie » : le LLM reste concentré sur les quelques morceaux qui comptent, plutôt que de parcourir du bruit.</p><p>Cela dit, l&#x27;intégration d&#x27;Elasticsearch ou de n&#x27;importe quelle mémoire vectorielle dans une architecture d&#x27;agent d&#x27;IA offre une <strong>mémoire à long terme</strong>. L&#x27;agent stocke les connaissances à l&#x27;extérieur et les intègre au contexte de la mémoire en cas de besoin. Cela pourrait être implémenté sous forme <em>d’architecture</em>, où après chaque requête utilisateur, l’agent effectue une recherche sur Elasticsearch pour des informations pertinentes puis ajoute les premiers résultats à l’invite avant d’appeler le LLM. La réponse peut également être enregistrée dans le stockage à long terme si elle contient de nouvelles informations utiles (création d&#x27;une boucle d&#x27;apprentissage). En utilisant cette mémoire basée sur la récupération, l’agent reste informé et à jour, sans avoir à tout condenser dans chaque invite, même si la fenêtre de contexte prend en charge <em>un million de tokens</em>. Cette technique est une pierre angulaire de l&#x27;ingénierie contextuelle, combinant les forces de la récupération d&#x27;informations et de l&#x27;IA générative. </p><p>Voici un exemple d&#x27;état de conversation géré en mémoire utilisant le système de points de contrôle de LangGraph pour la mémoire à court terme pendant la session. (Reportez-vous à notre <a href="https://github.com/someshwaranM/elastic-context-engineering-short-term-long-term-memory">application d&#x27;ingénierie contextuelle</a>.)</p><pre><code># Initialize chat memory (Note: This is in-memory only, not persistent)
memory = MemorySaver()

# Create a LangGraph agent
langgraph_agent = create_react_agent(model=llm, tools=tools, checkpointer=memory)

...
...
# Only process and display checkpoints if verbose mode is enabled
if args.verbose:
    # List all checkpoints that match a given configuration
    checkpoints = memory.list({"configurable": {"thread_id": "1"}})
    # Process the checkpoints
    process_checkpoints(checkpoints)</code></pre><p>Voici comment il stocke les <strong>points de contrôle</strong> :</p><pre><code>Checkpoint:
Timestamp: 2025-12-30T09:19:41.691087+00:00
Checkpoint ID: 1f0e560a-c2fa-69ec-8001-14ee5373f9cf
User: Hi I'm Som, how are you? (Message ID: ad0a8415-5392-4a58-85ad-84154875bbf2)
Agent: Hi Som! I'm doing well, thank you! How about you? (Message ID: 
56d31efb-14e3-4148-806e-24a839799ece)
Agent:  (Message ID: lc_run--019b6e8e-553f-7b52-8796-a8b1fbb206a4-0)

Checkpoint:
Timestamp: 2025-12-30T09:19:40.350507+00:00
Checkpoint ID: 1f0e560a-b631-6a08-8000-7796d108109a
User: Hi I'm Som, how are you? (Message ID: ad0a8415-5392-4a58-85ad-84154875bbf2)
Agent: Hi Som! I'm doing well, thank you! How about you? (Message ID: 
56d31efb-14e3-4148-806e-24a839799ece)

Checkpoint:
Timestamp: 2025-12-30T09:19:40.349027+00:00
Checkpoint ID: 1f0e560a-b62e-6010-bfff-cbebe1d865f6</code></pre><p>Pour la mémoire à long terme, voici comment nous effectuons une recherche sémantique sur Elasticsearch pour récupérer des conversations précédentes pertinentes en utilisant des vecteurs d&#x27;intégration après la résumé et l&#x27;indexation des points de contrôle dans Elasticsearch.</p><pre><code>Functions: 
retrieve_from_elasticsearch() 

# Enhanced Elasticsearch retrieval with rank_window and verbose display
def retrieve_from_elasticsearch(query: str, k: int = 5, rank_window: int = None) -> tuple[List[Dict[str, Any]], str]:
    """
    Retrieve context from Elasticsearch with score-based ranking
    
    Args:
        query: Search query
        k: Number of results to return
        rank_window: Number of candidates to retrieve before ranking (default: args.rank_window)
        
    Returns:
        Tuple of (retrieved_documents, formatted_context_string)
    """
    if not es_client or not es_index_name:
        return [], "Elasticsearch is not available. Cannot search long-term memory."
    
    if rank_window is None:
        rank_window = args.rank_window
    
    try:
        # Check if index exists and has documents
        if not es_client.indices.exists(index=es_index_name):
            return [], "No previous conversations stored in long-term memory yet."
        
        # Get document count
        try:
            doc_count = es_client.count(index=es_index_name)["count"]
            if doc_count == 0:
                return [], "Long-term memory is empty. No previous conversations to search."
        except Exception as e:
            return [], f"Error checking memory: {str(e)}"
        
        # Generate embedding for the query
        try:
            query_embedding = embeddings.embed_query(query)
        except Exception as e:
            return [], f"Error generating embedding: {str(e)}"
        
        # Perform semantic search using kNN with rank_window
        try:
            search_body = {
                "knn": {
                    "field": "vector",
                    "query_vector": query_embedding,
                    "k": k,
                    "num_candidates": rank_window  # Retrieve more candidates, then rank top k
                },
                "_source": ["text", "content", "message_type", "timestamp", "thread_id"],
                "size": k
            }
            
            response = es_client.search(index=es_index_name, body=search_body)
            
            if not response.get("hits") or len(response["hits"]["hits"]) == 0:
                return [], "No relevant previous conversations found in long-term memory."
            
            # Extract documents with scores
            retrieved_docs = []
            for hit in response["hits"]["hits"]:
                source = hit["_source"]
                score = hit["_score"]
                retrieved_docs.append({
                    "content": source.get("content", source.get("text", "")),
                    "message_type": source.get("message_type", "unknown"),
                    "timestamp": source.get("timestamp", "unknown"),
                    "thread_id": source.get("thread_id", "unknown"),
                    "score": score
                })
            
            # Format context string
            context_parts = []
            for i, doc in enumerate(retrieved_docs, 1):
                context_parts.append(doc["content"])
            
            context_string = "\n\n".join(context_parts)
            
            # Verbose display
            if args.verbose:
                rich.print(f"\n[bold yellow]🔍 RETRIEVAL ANALYSIS[/bold yellow]")
                rich.print("="*80)
                rich.print(f"[blue]Query:[/blue] {query}")
                rich.print(f"[blue]Retrieved:[/blue] {len(retrieved_docs)} documents (from {rank_window} candidates)")
                rich.print(f"[blue]Total context length:[/blue] {len(context_string)} characters\n")
                
                for i, doc in enumerate(retrieved_docs, 1):
                    rich.print(f"[cyan]📄 Document {i} | Score: {doc['score']:.4f} | Type: {doc['message_type']}[/cyan]")
                    rich.print(f"[cyan]   Timestamp: {doc['timestamp']} | Thread: {doc['thread_id']}[/cyan]")
                    content_preview = doc['content'][:200] + "..." if len(doc['content']) > 200 else doc['content']
                    rich.print(f"[cyan]   Content: {content_preview}[/cyan]")
                    rich.print("-" * 80)
            
            return retrieved_docs, context_string
            
        except Exception as e:
            return [], f"Error searching memory: {str(e)}"
            
    except Exception as e:
        return [], f"Error accessing long-term memory: {str(e)}"</code></pre><p>Maintenant que nous avons exploré comment la mémoire à court terme et la mémoire à long terme sont indexées et récupérées à l’aide des points de contrôle de LangGraph dans Elasticsearch, prenons un moment pour comprendre pourquoi l&#x27;indexation et le vidage des conversations complètes peut être risqué.</p><h2>Risques liés à une mauvaise gestion de la mémoire de contexte</h2><p>Alors que nous parlons beaucoup d’ingénierie du contexte, ainsi que de la mémoire à court et à long terme, comprenons ce qui se passe si nous ne gérons pas bien la mémoire et le contexte d’un agent.</p><p>Malheureusement, de nombreux problèmes peuvent survenir lorsque le contexte d’une IA devient extrêmement long ou contient des informations erronées. Au fur et à mesure que les fenêtres contextuelles s’agrandissent, de <strong>nouveaux modes de défaillance</strong> apparaissent, comme :</p><ul><li><strong>Empoisonnement contextuel</strong></li><li><strong>Distraction contextuelle</strong></li><li><strong>Confusion de contexte</strong></li><li><strong>Conflit de contexte</strong></li><li><strong>Fuite de contexte et conflits de connaissances</strong></li><li><strong>Hallucinations et désinformation.</strong></li></ul><p>Examinons ces problèmes et les autres risques qui découlent d&#x27;une mauvaise gestion du contexte :</p><h3>Empoisonnement contextuel</h3><p><em>L&#x27;empoisonnement du contexte</em> fait référence au fait que des informations incorrectes ou nuisibles apparaissent dans le contexte et « empoisonne » les sorties ultérieures du modèle. Un exemple courant est une hallucination du modèle qui est traitée comme un fait et insérée dans l&#x27;historique de la conversation. Le modèle pourrait alors s&#x27;appuyer sur cette erreur dans les réponses ultérieures, aggravant la faute. Dans les boucles itératives d&#x27;agents, une fois qu&#x27;une fausse information s&#x27;est intégrée dans le contexte partagé (par exemple, dans un résumé des notes de travail de l&#x27;agent), elle peut être renforcée à maintes reprises. </p><p><a href="https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf">Les chercheurs de DeepMind, dans la publication du rapport Gemini 2.5</a> (TL;DR, consultez <a href="https://www.dbreunig.com/2025/06/17/an-agentic-case-study-playing-pok%C3%A9mon-with-gemini.html">ici</a>), ont observé ce phénomène chez un agent <em>joueur de Pokémon de</em>longue date : si l&#x27;agent hallucinait un état de jeu erroné et que celui-ci était enregistré dans son <em>contexte </em>(sa mémoire des objectifs), il formait des <strong>stratégies absurdes</strong> autour d&#x27;un objectif impossible et se retrouvait bloqué. En d&#x27;autres termes, une mémoire empoisonnée peut envoyer l&#x27;agent sur la mauvaise voie indéfiniment.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3f2facf5aad67613ad557422e09ec23a66adc0ed-1600x1388.png" style="width: 100%;" /><p>L&#x27;empoisonnement du contexte peut se produire de manière innocente (par erreur) ou même de manière malveillante, par exemple via des attaques par injection de requêtes où un utilisateur ou un tiers introduit une instruction cachée ou une fausse information que l&#x27;agent mémorise ensuite et suit.</p><p><strong>Contre-mesures recommandées :</strong></p><p>S’appuyant sur les informations de <a href="https://www.wiz.io/academy/data-poisoning">Wiz</a>, <a href="https://zerlo.net/en/blog/what-is-llm-data-poisoning">Zerlo</a> et <a href="https://www.anthropic.com/research/small-samples-poison">Anthropic</a>, les contre-mesures contre l’empoisonnement du contexte visent à empêcher que des informations erronées ou trompeuses ne pénètrent dans l’invite, la fenêtre de contexte ou le pipeline de récupération d’un LLM. Parmi les principales étapes, citons :</p><ul><li>Vérifiez constamment le contexte : surveillez la conversation ou le texte récupéré afin de détecter tout élément suspect ou nuisible, et pas seulement l’invite de départ.</li><li>Utilisez des sources fiables : attribuez une note ou un label aux documents en fonction de leur crédibilité afin que le système privilégie les informations fiables et ignore les données mal notées.</li><li>Repérez les données inhabituelles : utilisez des outils qui détectent les contenus bizarres, déplacés ou manipulés, et supprimez-les avant que le modèle ne les utilise.</li><li>Filtrez les entrées et les sorties : ajoutez des garde-fous pour que les textes nuisibles ou trompeurs ne puissent pas facilement entrer dans le système ou être répétés par le modèle.</li><li>Mettez le modèle à jour avec des données propres : actualisez régulièrement le système avec des informations vérifiées afin de corriger les éventuelles données erronées.</li><li>Supervision humaine : faites examiner les sorties importantes par des personnes ou comparez-les à des sources connues et fiables.</li></ul><p>De simples habitudes utilisateur sont également utiles : réinitialiser les longues conversations, ne partager que les informations pertinentes, découper les tâches complexes en étapes plus simples et conserver des notes claires en dehors du modèle.</p><p>Ensemble, ces mesures créent une défense en couches qui protège les LLM contre l&#x27;empoisonnement du contexte et maintient les sorties précises et dignes de confiance.</p><p>Sans les contre-mesures mentionnées ici, un agent pourrait se souvenir d&#x27;instructions, comme ignorer des lignes directrices antérieures<em> </em>ou des faits triviaux insérés par un attaquant, ce qui conduirait à des sorties nuisibles.</p><h3>Distraction contextuelle</h3><p>On parle de <em>distraction contextuelle</em> lorsqu&#x27;un contexte devient si long que le modèle se concentre excessivement sur le contexte, négligeant ce qu&#x27;il a appris pendant la formation. Dans les cas extrêmes, cela ressemble à un <a href="https://en.wikipedia.org/wiki/Catastrophic_interference"><em>oubli catastrophique</em></a>, c&#x27;est-à-dire que le modèle « oublie » effectivement ses connaissances sous-jacentes et s&#x27;attache excessivement aux informations qui lui sont présentées. Des études précédentes ont montré que les LLM perdent souvent leur concentration lorsque l&#x27;invite est extrêmement longue.</p><p>L&#x27;agent Gemini 2.5, par exemple, prenait en charge une fenêtre d&#x27;un million de jetons, mais dès que son contexte dépassait un certain seuil (de l&#x27;ordre de 100 000 jetons dans une expérience), il commençait à <strong>s&#x27;attacher à répéter ses actions passées</strong> au lieu de proposer de nouvelles solutions. En un sens, l’agent est devenu prisonnier de sa longue histoire. Il a continué à regarder son long log de mouvements précédents (le contexte) et à les imiter, plutôt que d&#x27;utiliser ses connaissances d&#x27;entraînement sous-jacentes pour concevoir des stratégies nouvelles et inédites.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e08e5b6d2e8ec7e3511d455985eed3d7fa6241e0-1352x636.png" style="width: 100%;" /><p>C&#x27;est contreproductif. Nous voulons que le modèle utilise un contexte pertinent pour faciliter le raisonnement, et non qu&#x27;il prenne le pas sur sa capacité de réflexion. Fait notable, même les modèles disposant de fenêtres très larges présentent une forme de <a href="https://research.trychroma.com/context-rot"><em>dégradation du contexte</em></a> : leurs performances se détériorent de manière non uniforme à mesure que le nombre de jetons augmente. Il semble exister un <em>budget d’attention</em> : tout comme les humains ont une mémoire de travail limitée, un LLM dispose d’une capacité finie pour traiter les jetons, et plus ce budget est sollicité, plus sa précision et sa concentration diminuent.</p><p>Pour atténuer ce problème, vous pouvez empêcher la distraction contextuelle en utilisant la segmentation, en concevant les bonnes informations, en résumant régulièrement le contexte et en appliquant des techniques d’évaluation et de surveillance pour mesurer la précision de la réponse à l’aide de la notation.</p><p>Ces méthodes permettent au modèle de rester ancré dans un contexte pertinent et dans sa formation sous-jacente, ce qui réduit le risque de distraction et améliore la qualité globale du raisonnement.</p><h3>Confusion de contexte</h3><p>La <em>confusion contextuelle</em> se produit lorsque le modèle utilise du contenu superflu dans le contexte pour générer une réponse de faible qualité. Un bon exemple est de fournir à un agent un large éventail d&#x27;outils ou de définitions d&#x27;API qu&#x27;il peut utiliser. Si bon nombre de ces outils n&#x27;ont aucun rapport avec la tâche en cours, le modèle peut tout de même essayer de les utiliser de manière inappropriée, simplement parce qu&#x27;ils sont présents dans le contexte. Les expériences ont montré que fournir <em>plus</em> d&#x27;outils ou de documents peut <em>nuire</em> aux performances s&#x27;ils ne sont pas tous nécessaires. L’agent commence à faire des erreurs, comme appeler la mauvaise fonction ou référer un texte sans importance. </p><p>Dans un cas, un petit modèle <strong>Llama 3.1 8B</strong> a échoué à une tâche lorsqu&#x27;on lui a donné 46 outils à prendre en compte, mais a réussi lorsqu&#x27;on ne lui a donné que 19 outils. Ces outils supplémentaires ont créé de la confusion, même si le contexte respectait les limites de longueur. Le problème sous-jacent est que toute information contenue dans l&#x27;invite sera <em>prise en compte</em> par le modèle. Si un système ne sait pas ignorer quelque chose, ce quelque chose pourrait influencer sa sortie de manière indésirable. Des éléments non pertinents peuvent « détourner » une partie de l&#x27;attention du modèle et l&#x27;induire en erreur (par exemple, un document non pertinent pourrait amener l&#x27;agent à répondre à une question différente de celle posée). La confusion contextuelle se manifeste souvent par la production, par le modèle, d&#x27;une réponse de faible qualité intégrant un contexte non pertinent. Se référer à l&#x27;article de recherche : <a href="https://arxiv.org/pdf/2411.15399">Less is More: Optimizing Function Calling for LLM Execution on Edge Devices.</a></p><p>Cela nous rappelle qu&#x27;il n&#x27;est pas toujours préférable d&#x27;avoir plus de contexte, surtout si ce n&#x27;est pas <strong>organisé</strong> pour des raisons de pertinence.</p><h3>Conflit de contexte</h3><p>Il y a <em>conflit de contexte</em> lorsque <strong>des éléments du contexte se contredisent</strong>, provoquant des incohérences internes qui font dérailler le raisonnement du modèle. Un conflit peut survenir si l&#x27;agent accumule plusieurs éléments d&#x27;information qui sont en conflit. </p><p>Par exemple, imaginez un agent qui a récupéré des données de deux sources : l&#x27;une dit <em>Le vol A part à 17 h</em>, et l&#x27;autre dit <em>Le vol A part à 18 h</em>. Si les deux faits se retrouvent dans le contexte, le modèle pauvre n&#x27;a aucun moyen de savoir lequel est correct ; il peut s&#x27;embrouiller ou produire une réponse incorrecte ou non similaire.</p><p>Le conflit de contexte se produit aussi fréquemment dans les conversations à plusieurs tours, lorsque les <strong>tentatives</strong> de réponse antérieures du modèle sont encore présentes dans le contexte avec des informations affinées ultérieurement.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/500d7a80dc8db1923f9b5ca84728eed64fa296f7-1316x580.png" style="width: 100%;" /><p>Une <a href="https://arxiv.org/pdf/2505.06120">étude menée</a> par Microsoft et Salesforce montre que si l&#x27;on divise une requête complexe en plusieurs échanges avec un chatbot (en ajoutant progressivement des détails), la précision finale diminue considérablement, comparée à la fourniture de tous les détails en une seule requête. Pourquoi ? Parce que les premiers tours contiennent des réponses intermédiaires partielles ou incorrectes du modèle, et que celles-ci restent dans le contexte. Lorsque le modèle tente par la suite de répondre avec toutes les informations, sa <em>mémoire</em> contient encore ces tentatives erronées, qui entrent en conflit avec les informations corrigées et l&#x27;éloignent de la bonne voie. En substance, le contexte de la conversation entre en conflit avec lui-même. Le modèle peut utiliser par inadvertance un élément de contexte obsolète (d&#x27;un tour précédent) qui ne s&#x27;applique plus après l&#x27;ajout de nouvelles informations.</p><p>Dans les systèmes agents, le conflit de contexte est particulièrement dangereux, car un agent peut combiner des sorties provenant de différents outils ou sous-agents. Si ces sorties divergent, le contexte agrégé est incohérent. L&#x27;agent pourrait alors se retrouver bloqué ou produire des résultats absurdes en essayant de réconcilier les contradictions. Prévenir les conflits de contexte implique de s’assurer que le contexte est <strong>frais et cohérent</strong>,<strong> </strong>par exemple en effaçant .ou en mettant à jour toute information obsolète et en ne mélangeant pas les sources qui n’ont pas été vérifiées pour leur cohérence.</p><h3>Fuite de contexte et conflits de connaissances</h3><p>Dans les systèmes où plusieurs agents ou utilisateurs partagent un stockage de mémoire, il existe un risque de fuite d&#x27;informations entre les contextes.</p><p>Par exemple, si les intégrations de données de deux utilisateurs distincts résident dans la même base vectorielle sans un contrôle d’accès approprié, un agent répondant à la requête de l’utilisateur A pourrait accidentellement récupérer une partie de la mémoire de l’utilisateur B. Cette <strong><em>fuite intercontextuelle</em></strong> peut révéler des informations privées ou simplement créer de la confusion dans les réponses.</p><p>Selon le <a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/">Top 10 de l&#x27;OWASP pour les applications LLM</a>, les bases vectorielles multitenant doivent se prémunir contre de telles fuites :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f0ccf0b2f7bd6715c14aceee2deffb213d50bd9-1600x936.png" style="width: 100%;" /><p>Selon <a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/">LLM08:2025 Vector and Embedding Weaknesses</a><em>,</em> l&#x27;un des risques courants est la fuite de contexte :</p><blockquote><em>Dans les environnements multi-locataires où plusieurs classes d’utilisateurs ou d’applications partagent la même base vectorielle, il existe un risque de fuite de contexte entre utilisateurs ou requêtes. Les erreurs de conflit de connaissances dans la fédération de données peuvent survenir lorsque les données provenant de sources multiples se contredisent les unes les autres. Cela peut également se produire lorsqu&#x27;un LLM ne peut pas remplacer les anciennes connaissances qu&#x27;il a acquises pendant la formation par les nouvelles données issues de l&#x27;augmentation de la récupération.</em></blockquote><p>Un autre aspect est qu&#x27;un LLM peut avoir du mal à remplacer ses <strong>connaissances intégrées</strong> par de nouvelles informations de mémoire. Si le modèle a été formé sur la base d&#x27;un fait et que le contexte retrouvé dit le contraire, le modèle peut ne pas savoir à quoi se fier. Sans une conception appropriée, l&#x27;agent pourrait confondre les contextes ou ne pas mettre à jour les anciennes connaissances avec de nouvelles preuves, conduisant à des réponses obsolètes ou incorrectes.</p><h3><strong>Hallucinations et désinformation.</strong></h3><p>Si l&#x27;<em>hallucination </em>(le LLM invente des informations plausibles mais fausses) est un problème connu, même sans contexte prolongé, une mauvaise gestion de la mémoire peut l&#x27;amplifier. </p><p>Si la mémoire de l&#x27;agent manque d&#x27;un fait crucial, le modèle peut simplement <strong>combler cette lacune par une supposition</strong>, et si cette supposition entre ensuite dans le contexte (en l&#x27;empoisonnant), l&#x27;erreur persiste. </p><p>Le rapport de sécurité OWASP LLM <a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/"><strong>(LLM09:2025 Désinformation)</strong></a> met en évidence la désinformation comme une vulnérabilité fondamentale : les LLM peuvent produire des réponses confiantes mais fabriquées, et les utilisateurs peuvent leur accorder trop de crédit. Un agent dont la mémoire à long terme est mauvaise ou obsolète peut citer en toute confiance une information qui était vraie l&#x27;année dernière mais qui est fausse aujourd&#x27;hui, à moins que sa mémoire ne soit mise à jour. </p><p>Une dépendance excessive à la sortie de l&#x27;IA (par l&#x27;utilisateur ou l&#x27;agent lui-même dans la boucle) peut aggraver cette situation. Si personne ne vérifie jamais les informations en mémoire, l&#x27;agent peut accumuler de fausses informations. C’est pourquoi la méthode RAG est souvent utilisée pour réduire les hallucinations : en se référant à une source faisant autorité, le modèle n’a pas besoin d’inventer des faits. Mais si votre recherche aboutit au mauvais document (par exemple, un document contenant des informations erronées) ou si une hallucination précoce n&#x27;est pas élaguée, le système peut propager ces informations erronées dans toutes ses actions. </p><p>En résumé : une mauvaise gestion de la mémoire peut conduire à des <strong>sorties incorrectes et trompeuses</strong>, ce qui peut être préjudiciable, surtout si les enjeux sont importants (par exemple, de mauvais conseils dans le domaine financier ou médical). Un agent doit disposer de mécanismes pour vérifier ou corriger le contenu de sa mémoire, et non simplement faire confiance de manière inconditionnelle à ce qui se trouve dans le contexte.</p><p>En résumé, doter un agent d&#x27;IA d&#x27;une mémoire infiniment longue ou déverser tout ce qui est possible dans son contexte <em>n&#x27;</em> est pas une recette pour le succès.</p><h2>Bonnes pratiques pour la gestion de la mémoire dans les applications LLM</h2><p>Pour éviter les pièges ci-dessus, les développeurs et les chercheurs ont élaboré un certain nombre de <strong>bonnes pratiques pour gérer le contexte et la mémoire</strong> dans les systèmes d&#x27;IA. Ces pratiques visent à maintenir le contexte de travail de l&#x27;IA allégé, pertinent et actualisé. Voici quelques-unes des stratégies clés, accompagnées d&#x27;exemples de leur utilité.</p><h3>RAG : utiliser le contexte ciblé.</h3><p>Une grande partie de RAG a déjà été abordée dans la section précédente, ceci constitue donc un rappel pratique et concis :</p><ul><li>Utilisez une récupération ciblée, pas un chargement en masse : récupérez uniquement les extraits les plus pertinents au lieu d&#x27;insérer des documents entiers ou des historiques de conversation complets dans l&#x27;invite.</li><li>Considérez RAG comme un rappel de mémoire à la demande : récupérez le contexte uniquement lorsqu’il est nécessaire, plutôt que de tout conserver d’un échange à l’autre.</li><li>Privilégiez des stratégies de récupération sensibles à la pertinence : des approches telles que la recherche sémantique top-k, la fusion de rangs réciproques (Reciprocal Rank Fusion) ou le filtrage par configuration d’outils permettent de réduire le bruit et d’améliorer l’ancrage.</li><li>Des fenêtres de contexte plus larges ne suppriment pas le besoin de RAG : deux paragraphes hautement pertinents sont presque toujours plus efficaces que 20 pages vaguement liées.</li></ul><p>Cela dit, RAG ne vise pas à ajouter plus de contexte ; il s’agit d’ajouter le bon contexte.</p><h3>Chargement des outils</h3><p><em>Le loadout d’outils</em> consiste à donner à un modèle uniquement les outils dont il a réellement besoin pour une tâche. Le terme vient du jeu : vous choisissez une configuration qui convient à la situation. Trop d&#x27;outils vous ralentissent ; les mauvais sont à l&#x27;origine de l&#x27;échec. Les LLM se comportent de la même manière, selon l&#x27;article de recherche <a href="https://arxiv.org/abs/2411.15399">Less is more</a>. Une fois que vous dépassez ~30 outils, les descriptions commencent à se chevaucher et le modèle se trouve dérouté. Plus de 100 outils, l&#x27;échec est presque garanti. Ce n’est pas un problème de fenêtre contextuelle, c’est une confusion de contexte.</p><p>Une solution simple et efficace est <a href="https://arxiv.org/abs/2505.03275"><strong>RAG-MCP</strong></a>. Au lieu de saisir tous les outils dans l&#x27;invite, les descriptions d&#x27;outils sont stockées dans une base vectorielle et seuls les outils les plus pertinents sont récupérés par demande. En pratique, cela permet de réduire la taille de l&#x27;équipement et de le concentrer, de raccourcir considérablement les instructions et d&#x27;améliorer la précision de la sélection des outils jusqu&#x27;à 3 fois.</p><p>Les modèles plus petits atteignent ce plafond encore plus tôt. La recherche montre qu&#x27;un modèle 8B échoue avec des dizaines d&#x27;outils mais réussit une fois que la configuration est réduite. La sélection dynamique des outils, parfois précédée d’une réflexion par un LLM sur ce dont il pense avoir besoin, peut améliorer les performances de 44 %, tout en réduisant la consommation d’énergie et la latence. La principale leçon est que la plupart des agents n’ont besoin que de quelques outils, mais à mesure que votre système se développe, la configuration des outils et le RAG-MCP deviennent des choix de conception de premier ordre.</p><h3>Élagage du contexte : limiter la longueur de l&#x27;historique de conversation</h3><p>Si une conversation se poursuit sur de nombreux tours, l&#x27;historique de discussion accumulé peut devenir trop volumineux pour tenir, entraînant un débordement de contexte ou devenant trop distrayant pour le modèle. </p><p><em>Le rognage</em> consiste à supprimer ou à raccourcir par programmation les parties les moins importantes du dialogue au fur et à mesure qu&#x27;il grandit. Une forme simple consiste à supprimer les tours de parole les plus anciens lorsque vous atteignez une certaine limite, en ne conservant que les <em>N</em> derniers messages. Un élagage plus sophistiqué pourrait supprimer les digressions non pertinentes ou les instructions précédentes devenues inutiles. L&#x27;objectif est de <strong>ne pas encombrer la fenêtre contextuelle</strong> par les anciennes actualités. </p><p>Par exemple, si l’agent a résolu un sous-problème il y a 10 échanges et que nous sommes passés à autre chose depuis, nous pourrions supprimer cette partie de l’historique du contexte (en supposant qu’elle ne sera plus nécessaire). De nombreuses implémentations basées sur le chat font cela : elles assurent la maintenance d&#x27;une fenêtre dynamique de messages récents. </p><p>La suppression peut être aussi simple que le fait d’« oublier » les premières parties d’une conversation une fois qu’elles ont été résumées ou jugées non pertinentes. Ce faisant, nous réduisons le risque d&#x27;erreurs de débordement du contexte et nous réduisons également la <a href="https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch#context-distraction"><strong>distraction du contexte</strong></a>, de sorte que le modèle ne voit pas et ne se laisse pas distraire par un contenu ancien ou hors sujet. Cette approche ressemble beaucoup à celle des humains, qui ne se souviennent peut-être pas de chaque mot d’une conférence d’une heure, mais en retiennent les points essentiels. </p><p>Si vous avez des doutes sur l&#x27;élagage de contexte, comme le souligne l&#x27;auteur Drew Breunig <a href="https://www.dbreunig.com/2025/06/26/how-to-fix-your-context.html#tool-loadout:~:text=Provence%20is%20fast%2C%20accurate%2C%20simple%20to%20use%2C%20and%20relatively%20small%20%E2%80%93%20only%201.75%20GB.%20You%20can%20call%20it%20in%20a%20few%20lines%2C%20like%20so%3A">ici</a>, l&#x27;utilisation du modèle Provence (<a href="https://huggingface.co/naver/provence-reranker-debertav3-v1">`naver/provence-reranker-debertav3-v1`</a>), un élagueur de contexte léger (1,75 Go), efficace et précis pour la réponse aux questions, peut faire la différence. Il peut réduire de gros documents à seulement le texte le plus pertinent pour une requête donnée. Vous pouvez l&#x27;appeler à des intervalles précis.</p><p>Voici comment nous invoquons le modèle `provence-reranker` dans notre code pour élaguer le contexte :</p><pre><code># Context pruning with Provence
def prune_with_provence(query: str, context: str, threshold: Optional[float] = None) -> str:
    """
    Prune context using Provence reranker model
    
    Args:
        query: User's query/question
        context: Original context to prune
        threshold: Relevance threshold (0-1) for Provence reranker.
                   If None, uses args.pruning_threshold.
                   0.1 = conservative (recommended, no performance drop)
                   0.3-0.5 = moderate to aggressive pruning
    
    Returns:
        Pruned context with only relevant sentences
    """
    if provence_model is None:
        return context
    
    if threshold is None:
        threshold = args.pruning_threshold
    
    try:
        # Use Provence's process method
        provence_output = provence_model.process(
            question=query,
            context=context,
            threshold=threshold,
            always_select_title=False,
            enable_warnings=False
        )
        
        # Extract pruned context from output
        pruned_context = provence_output.get('pruned_context', context)
        reranking_score = provence_output.get('reranking_score', 0.0)
        
        # Log statistics
        original_length = len(context)
        pruned_length = len(pruned_context)
        reduction_pct = ((original_length - pruned_length) / original_length * 100) if original_length > 0 else 0
        
        if args.verbose:
            rich.print(f"[cyan]📊 Pruning stats: {pruned_length}/{original_length} chars ({reduction_pct:.1f}% reduction, threshold={threshold:.2f}, rerank_score={reranking_score:.3f})[/cyan]")
        
        return pruned_context if pruned_context else context
        
    except Exception as e:
        rich.print(f"[yellow]⚠️ Error in Provence pruning: {str(e)}[/yellow]")
        rich.print(f"[yellow]⚠️ Falling back to original context[/yellow]")
        return context</code></pre><p>Nous utilisons le modèle de reranker Provence (`naver/provence-reranker-debertav3-v1`) pour évaluer la pertinence des phrases. La filtration basée sur des seuils conserve les phrases au-dessus du seuil de pertinence. Nous introduisons également un mécanisme de repli, qui permet de revenir au contexte d&#x27;origine en cas d&#x27;échec de l&#x27;élagage. Enfin, le logging des statistiques permet de suivre le pourcentage de réduction en mode verbeux.</p><h3>Synthèse du contexte : condenser les anciennes informations au lieu de les supprimer entièrement</h3><p><em>Le résumé va</em> de pair avec le découpage. Lorsque l’historique ou la base de connaissances devient trop vaste, vous pouvez utiliser le LLM pour générer un bref résumé des points importants et utiliser ce résumé à la place du contenu complet par la suite, comme nous l’avons fait dans notre code ci-dessus.</p><p>Par exemple, si un assistant IA a eu une conversation de 50 tours, au lieu d&#x27;envoyer tous les 50 tours au modèle au tour 51 (ce qui ne rentrera probablement pas), le système pourrait prendre les tours 1 à 40, demander au modèle de les résumer en un paragraphe, et ensuite ne fournir que ce résumé plus les 10 derniers tours dans la prochaine invite. De cette façon, le modèle reste conscient de ce qui a été discuté sans avoir besoin de tous les détails. Les premiers utilisateurs de chatbots faisaient cela manuellement en demandant : « Pouvez-vous résumer ce dont nous avons parlé jusqu&#x27;à présent ? » puis en continuant dans une nouvelle session avec le résumé. Maintenant, cela peut être automatisé. Le résumé permet non seulement d&#x27;économiser de l&#x27;espace dans la fenêtre contextuelle, mais aussi de réduire la <strong>confusion et la distraction</strong> en éliminant les détails supplémentaires et en ne conservant que les faits saillants.</p><p>Voici comment nous utilisons les modèles OpenAI (vous pouvez utiliser n’importe quel LLM) pour condenser le contexte tout en préservant toutes les informations pertinentes, en éliminant la redondance et la duplication.<br/></p><pre><code># Context summarization
def summarize_context(query: str, context: str) -> str:
    """
    Summarize context using LLM to reduce duplication and focus on relevant information
    
    Args:
        query: User's query/question
        context: Context to summarize
        
    Returns:
        Summarized context
    """
    try:
        summary_prompt = f"""You are an expert at summarizing conversation context.

Your task: Analyze the provided conversation context and produce a condensed summary that fully answers or supports the user's specific question.

The summary must:
1. Preserve every fact, detail, and information that directly relates to the question
2. Eliminate redundancy and duplicate information
3. Maintain chronological flow when relevant
4. Focus on information that helps answer: "{query}"

Context to summarize:
{context}

Provide a concise summary that preserves all relevant information:"""

        summary = llm.invoke(summary_prompt).content
        
        if args.verbose:
            original_length = len(context)
            summary_length = len(summary)
            reduction_pct = ((original_length - summary_length) / original_length * 100) if original_length > 0 else 0
            rich.print(f"[cyan]📝 Summarization stats: {summary_length}/{original_length} chars ({reduction_pct:.1f}% reduction)[/cyan]")
        
        return summary
        
    except Exception as e:
        rich.print(f"[yellow]⚠️ Error in context summarization: {str(e)}[/yellow]")
        rich.print(f"[yellow]⚠️ Falling back to original context[/yellow]")
        return context</code></pre><p>Il est important de noter que lorsque le contexte est résumé, le modèle est moins susceptible d&#x27;être submergé par des détails insignifiants ou des erreurs passées (en supposant que le résumé soit exact). </p><p>Cependant, le résumé doit être fait avec soin. Un mauvais résumé peut omettre un détail crucial ou même introduire une erreur. Il s’agit essentiellement d’une autre invite adressée au modèle (« résumez ceci »), ce qui peut entraîner des hallucinations ou une perte de nuances. Une bonne pratique consiste à résumer de manière incrémentielle et peut-être conserver certains faits canoniques non résumés.</p><p>Néanmoins, il s&#x27;est avéré très utile. <a href="https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf">Dans le scénario de l&#x27;agent Gemini, le </a>fait de résumer le contexte tous les 100 000 jetons environ a permis de contrecarrer la tendance du modèle à se répéter. Le résumé agit comme une mémoire compressée de la conversation ou des données. En tant que développeurs, nous pouvons mettre cela en œuvre en demandant à un agent d&#x27;appeler périodiquement une fonction de résumé (peut-être un LLM plus petit ou une routine dédiée) sur l&#x27;historique de la conversation ou un long document. Le résumé résultant remplace le contenu original dans la consigne. Cette tactique est largement utilisée pour limiter les contextes et distiller l&#x27;information.</p><h3>Quarantaine contextuelle : isolez les contextes lorsque c&#x27;est possible</h3><p>Cela est plus pertinent dans les systèmes d&#x27;agents complexes ou les workflows à plusieurs étapes. L&#x27;idée de la segmentation du contexte est de diviser une grande tâche en sous-tâches plus petites et isolées, chacune ayant son propre contexte, de sorte que vous n&#x27;accumuliez jamais un contexte énorme qui contient tout. Chaque sous-agent ou sous-tâche travaille sur une partie du problème dans un contexte précis, puis un agent de niveau supérieur, un superviseur ou un coordinateur intègre les résultats.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f2de68c3339883d7658e633af3948f29f427e6cf-1600x900.png" style="width: 100%;" /><p><a href="https://www.anthropic.com/engineering/multi-agent-research-system">La stratégie de recherche d’Anthropic utilise plusieurs sous-agents</a>, chacun examinant un aspect différent d’une question, avec ses propres fenêtre de contexte, et un agent principal qui lit les résultats synthétisés de ces sous-agents. Cette approche parallèle et modulaire signifie qu&#x27;aucune fenêtre contextuelle unique ne devient trop volumineuse. Cela réduit également le risque de mélange d&#x27;informations non pertinentes, chaque fil de discussion reste sur le sujet (pas de confusion de contexte), et il ne transporte pas de bagages superflus lors de la réponse à sa sous-question spécifique. Dans un sens, c&#x27;est comme suivre des fils de réflexion distincts qui ne partagent que leurs résultats, et non l&#x27;ensemble de leur processus de réflexion.</p><p>Dans les systèmes multi-agents, cette approche est essentielle. Si l&#x27;agent A gère la tâche A et l&#x27;agent B gère la tâche B, il n&#x27;y a aucune raison pour que l&#x27;un ou l&#x27;autre agent consomme le contexte complet de l&#x27;autre, sauf si c&#x27;est vraiment nécessaire. Les agents ne peuvent échanger que les informations nécessaires. Par exemple, l&#x27;agent A peut transmettre un résumé consolidé de ses résultats à l&#x27;agent B via un agent superviseur, tandis que chaque sous-agent assure la maintenance de son propre fil de contexte dédié. Cette configuration ne nécessite pas d&#x27;intervention humaine ; elle repose sur un agent de supervision doté d&#x27;outils activés avec un partage de contexte minimal et contrôlé.</p><p>Néanmoins, concevoir votre système de manière à ce que les agents ou les outils fonctionnent avec un chevauchement minimal du contexte nécessaire peut grandement améliorer la clarté et les performances. Pensez-y comme à des <strong>microservices pour l&#x27;IA</strong>, chaque composant s&#x27;occupe de son contexte, et vous passez des messages entre eux de manière contrôlée, au lieu d&#x27;un contexte monolithique. Ces bonnes pratiques sont souvent utilisées de manière combinée. Cela vous offre également la possibilité de supprimer l&#x27;historique trivial, de résumer les anciens messages ou conversations importants, de décharger les logs détaillés vers Elasticsearch pour un contexte à long terme et d&#x27;utiliser la récupération pour retrouver tout élément pertinent en cas de besoin.</p><p>Comme indiqué <a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=While%20some%20models,to%20the%20LLM">ici</a>, le principe directeur est que le contexte est une ressource limitée et précieuse. Vous souhaitez que chaque élément de l&#x27;invite soit utile, c&#x27;est-à-dire qu&#x27;il contribue à la qualité de la sortie. Si quelque chose en mémoire ne joue pas son rôle (ou pire, provoque activement de la confusion), alors il devrait être élagué, résumé ou tenu à l&#x27;écart.</p><p>En tant que développeurs, nous pouvons désormais programmer le contexte comme nous programmons du code, en décidant quelles informations inclure, comment les formater et quand les omettre ou les mettre à jour. En suivant ces pratiques, nous pouvons fournir aux agents LLM le contexte indispensable pour effectuer des tâches sans être victimes des modes de défaillance décrits précédemment. Le résultat : des agents qui retiennent ce qu’ils doivent, oublient ce qui leur est inutile et récupèrent ce dont ils ont besoin juste à temps.</p><h2>Conclusion</h2><p>La mémoire n&#x27;est pas quelque chose que l&#x27;on ajoute à un agent, c&#x27;est quelque chose que l&#x27;on développe. La mémoire à court terme est le bloc-notes de travail de l&#x27;agent, et la mémoire à long terme est son stock de connaissances durable. RAG est le pont entre les deux, transformant un datastore passif, comme Elasticsearch, en un mécanisme de rappel actif qui peut ancrer les sorties et maintenir l&#x27;agent à jour.</p><p>Mais la mémoire est une arme à double tranchant. Dès que vous laissez le contexte se développer sans contrôle, vous invitez l&#x27;empoisonnement, la distraction, la confusion et les conflits, et dans les systèmes partagés, même des fuites de données. C’est pourquoi le travail de mémoire le plus important n’est pas de « stocker davantage », mais de « mieux sélectionner » : récupérer de manière sélective, élaguer avec rigueur, résumer avec soin et éviter de mélanger des contextes non liés, sauf si la tâche l’exige réellement.</p><p>En pratique, une bonne ingénierie du contexte ressemble à une bonne conception de systèmes : des contextes plus petits et suffisants, des interfaces contrôlées entre les composants, et une séparation claire entre l&#x27;état brut et l&#x27;état distillé que vous voulez réellement que le modèle voie. Si l&#x27;on procède correctement, on ne se retrouve pas avec un agent qui se souvient de tout, mais avec un agent qui se souvient des bonnes choses, au bon moment et pour la bonne raison.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Débit plus élevé et latence plus faible : Elastic Cloud Serverless sur AWS bénéficie d'une amélioration significative des performances–]]></title>
            <description><![CDATA[Nous avons mis à niveau l'infrastructure AWS pour Elasticsearch Serverless vers du matériel plus récent et plus performant. Découvrez comment cette amélioration considérable des performances permettent des requêtes plus rapides, une scalabilité plus réactive et des coûts réduits.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-serverless-aws-performance-boost</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-serverless-aws-performance-boost</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Pete Galeotti, Yuvraj Gupta, Rachel Forshee]]></dc:creator>
            <pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3aa0b481994d2445ba979d3c79fff64c5ee6676a-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic Cloud Serverless est déjà la solution de référence pour les développeurs souhaitant créer des applications de recherche et d&#x27;IA performantes sans se soucier de la gestion de l&#x27;infrastructure. Désormais, les performances de vos projets sans serveur atteignent un tout autre niveau.</p><p>Nous avons effectué une mise à niveau majeure de l&#x27;infrastructure pour tous les projets <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a> exécutés sur AWS, en migrant vers du matériel plus récent et plus performant. Cette modification a été déployée automatiquement sur tous les projets sans serveur. Vous bénéficiez maintenant <strong>d&#x27;un débit plus élevé et d&#x27;une latence plus faible</strong> pour les projets sans serveur Elasticsearch, Elastic Observability et Elastic Security sur AWS.</p><h2><strong>Principaux avantages en termes de performances pour les développeurs</strong></h2><p>La nouvelle infrastructure matérielle d&#x27;AWS sous-tend tout ce que vous faites avec Elastic Cloud Serverless, ce qui se traduit par des avantages tangibles au niveau de la vitesse et de la réactivité de vos applications.</p><h3><strong>Latence des requêtes réduite… débit accru</strong></h3><p>Le matériel amélioré augmente considérablement la vitesse des ressources de calcul ; vos requêtes de recherche sont ainsi traitées plus rapidement que jamais.</p><ul><li><strong>Recherche et recherche vectorielle</strong> : que vous exécutiez des recherches full text traditionnelles ou que vous utilisiez la recherche vectorielle de pointe pour vos <a href="https://www.elastic.co/generative-ai">applications d&#x27;IA générative et de génération augmentée par récupération (RAG)</a>, vous constaterez une diminution notable de la latence. Une analyse comparative interne a révélé une diminution moyenne de 35 % de la latence de recherche.</li><li><strong>Indexation plus rapide</strong> : les taux d&#x27;ingestion de données sont optimisés, vous permettant d&#x27;indexer d&#x27;énormes volumes de données et des documents complexes avec un débit accru. Ceci est crucial pour les applications qui nécessitent une visibilité des données en temps quasi‑réel. L&#x27;évaluation comparative interne a montré une augmentation moyenne de 26 % du débit d&#x27;indexation.</li></ul><h3><strong>Performances constantes sous charge</strong></h3><p>Elastic Cloud Serverless est conçu pour s&#x27;adapter automatiquement et dynamiquement en temps réel à la demande, minimisant ainsi la latence, quelle que soit votre charge de travail. Grâce à cette amélioration matérielle, le scaling est désormais plus performant et réactif.</p><ul><li><strong>Gestion facile des pics de charge</strong> : qu&#x27;il s&#x27;agisse d&#x27;une augmentation soudaine du trafic utilisateur ou d&#x27;une ingestion massive de données par lots, la nouvelle infrastructure garantit un scaling vertical plus efficace sur vos ressources de recherche et d&#x27;indexation afin de maintenir une latence faible de façon constante.</li><li><strong>Découplage optimisé calcul‑stockage</strong> : l&#x27;architecture sans serveur sépare le calcul et le stockage, ce qui permet aux charges de travail de scaler indépendamment pour des performances et une rentabilité optimales. Le matériel plus rapide améliore la couche de calcul, maximisant l&#x27;efficacité de cette conception découplée.</li></ul><h2><strong>Sous le capot : résultats des analyses comparatives internes</strong></h2><p>Pour quantifier l&#x27;impact de la mise à niveau de notre infrastructure AWS, l&#x27;équipe d&#x27;ingénierie d&#x27;Elastic a mené des tests de performance internes complets sur un large éventail de charges de travail sans serveur. Ces charges de travail ont fourni des preuves concrètes des améliorations de performances que vous pouvez attendre de vos applications, quel que soit votre cas d&#x27;utilisation.</p><h3><strong>L&#x27;approche d&#x27;analyse comparative</strong></h3><p>Nous avons concentré nos tests sur les indicateurs clés qui influencent directement l&#x27;expérience développeur et la réactivité des applications : le temps de réponse (c&#x27;est-à-dire la latence) et le débit lors des opérations de recherche et d&#x27;indexation.</p><ul><li><strong>Test des charges de travail</strong> : les tests comprenaient des opérations de recherche à haute simultanéité typiques des applications destinées aux utilisateurs, des requêtes de recherche vectorielle complexes et l&#x27;ingestion/l&#x27;indexation de données à fort volume pour des cas d&#x27;utilisation d&#x27;observabilité et de sécurité. Plus précisément, notre méthodologie de test a utilisé des ensembles de données <a href="https://github.com/elastic/rally-tracks/tree/master">accessibles au public</a> <a href="https://github.com/elastic/rally-tracks/tree/master">pour Rally</a>, l&#x27;outil d&#x27;évaluation comparative d&#x27;Elastic.<ul><li><a href="https://github.com/elastic/rally-tracks/tree/3bedd51/wikipedia"><code>wikipedia</code></a>: Un ensemble de données issu d&#x27;un instantané du contenu textuel de Wikipédia, pour mesurer les performances de recherche textuelle à usage général.</li><li><a href="https://github.com/elastic/rally-tracks/tree/3bedd51/msmarco-passage-ranking"><code>MSMARCO-Passage-Ranking</code></a>: Un ensemble de données issu de Microsoft Machine Reading Comprehension (MS MARCO), pour mesurer les performances de recherche sur des champs vectoriels épars.</li><li><a href="https://github.com/elastic/rally-tracks/tree/3bedd51/openai_vector"><code>OpenAI_Vector</code></a>: Un ensemble de données issu du NQ de BEIR et enrichi d&#x27;embeddings générés par le modèle <code>text-embedding-ada-002</code> d&#x27;OpenAI, pour évaluer les performances de recherche sur des champs vectoriels denses.</li></ul></li><li><strong>Mesure</strong> : nous avons comparé les performances de l&#x27;ancienne et de la nouvelle infrastructure, en mesurant la latence au 99e percentile (P99) afin de capturer les performances les plus mauvaises et le nombre d&#x27;opérations par seconde, en tenant compte de la latence de queue. Chaque piste a été exécutée cinq fois pour chaque profil de matériel afin de garantir la cohérence des résultats.</li><li><strong>L&#x27;objectif</strong> : Notre objectif était de valider la capacité de l&#x27;infrastructure à fournir <strong>des performances plus rapides et plus prévisibles</strong> de manière constante, même pendant les périodes d&#x27;autoscaling rapide.</li></ul><h3><strong>Résumé des données de performance</strong></h3><p>Les résultats confirment des gains d&#x27;efficacité et de rapidité significatifs. Ces gains se traduisent directement par des temps de réponse plus courts pour vos utilisateurs et par une réduction des coûts opérationnels grâce à la possibilité d&#x27;effectuer la même quantité de travail avec moins de ressources de calcul.</p><p>Les tableaux suivants décrivent en détail les améliorations quantitatives. Plus les valeurs sont élevées, meilleur est le débit ; plus les valeurs sont faibles, meilleure est la latence.</p><p><strong>Résultats des recherches de référence :</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Référence</td><td style="border: 1px solid #ccc; padding: 4px;">Comparatif</td><td style="border: 1px solid #ccc; padding: 4px;">Ancienne infrastructure</td><td style="border: 1px solid #ccc; padding: 4px;">Nouvelle infrastructure</td><td style="border: 1px solid #ccc; padding: 4px;">Différentiel</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">'wikipedia' (texte brut)</td><td style="border: 1px solid #ccc; padding: 4px;">Débit des opérations de recherche (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">729</td><td style="border: 1px solid #ccc; padding: 4px;">1107</td><td style="border: 1px solid #ccc; padding: 4px;">+52 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">'wikipedia' (texte brut)</td><td style="border: 1px solid #ccc; padding: 4px;">Latence des opérations de recherche (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">56</td><td style="border: 1px solid #ccc; padding: 4px;">35</td><td style="border: 1px solid #ccc; padding: 4px;">-37 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking` (vecteurs épars)</td><td style="border: 1px solid #ccc; padding: 4px;">Débit des opérations de recherche (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">31</td><td style="border: 1px solid #ccc; padding: 4px;">+40 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking` (vecteurs épars)</td><td style="border: 1px solid #ccc; padding: 4px;">Latence des opérations de recherche (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">108</td><td style="border: 1px solid #ccc; padding: 4px;">67</td><td style="border: 1px solid #ccc; padding: 4px;">-38 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">'OpenAI_Vector' (vecteurs denses)</td><td style="border: 1px solid #ccc; padding: 4px;">Débit des opérations de recherche (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">475</td><td style="border: 1px solid #ccc; padding: 4px;">624</td><td style="border: 1px solid #ccc; padding: 4px;">+31 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">'OpenAI_Vector' (vecteurs denses)</td><td style="border: 1px solid #ccc; padding: 4px;">Latence des opérations de recherche (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">35</td><td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">-37 %</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>Résultats de référence de l&#x27;indexation :</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Référence</td><td style="border: 1px solid #ccc; padding: 4px;">Comparatif</td><td style="border: 1px solid #ccc; padding: 4px;">Ancienne infrastructure</td><td style="border: 1px solid #ccc; padding: 4px;">Nouvelle infrastructure</td><td style="border: 1px solid #ccc; padding: 4px;">Différentiel</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">'wikipedia' (texte brut)</td><td style="border: 1px solid #ccc; padding: 4px;">Débit des opérations de recherche (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">2 845</td><td style="border: 1px solid #ccc; padding: 4px;">3 220</td><td style="border: 1px solid #ccc; padding: 4px;">+13 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">'wikipedia' (texte brut)</td><td style="border: 1px solid #ccc; padding: 4px;">Latence des opérations de recherche (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">1769</td><td style="border: 1px solid #ccc; padding: 4px;">1120</td><td style="border: 1px solid #ccc; padding: 4px;">-37 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking` (vecteurs épars)</td><td style="border: 1px solid #ccc; padding: 4px;">Débit des opérations de recherche (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">7087</td><td style="border: 1px solid #ccc; padding: 4px;">8 900</td><td style="border: 1px solid #ccc; padding: 4px;">+26 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking` (vecteurs épars)</td><td style="border: 1px solid #ccc; padding: 4px;">Latence des opérations de recherche (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">824</td><td style="border: 1px solid #ccc; padding: 4px;">677</td><td style="border: 1px solid #ccc; padding: 4px;">-18 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">'OpenAI_Vector' (vecteurs denses)</td><td style="border: 1px solid #ccc; padding: 4px;">Débit des opérations de recherche (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">2972</td><td style="border: 1px solid #ccc; padding: 4px;">3187</td><td style="border: 1px solid #ccc; padding: 4px;">+7%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">'OpenAI_Vector' (vecteurs denses)</td><td style="border: 1px solid #ccc; padding: 4px;">Latence des opérations de recherche (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">2946</td><td style="border: 1px solid #ccc; padding: 4px;">2944</td><td style="border: 1px solid #ccc; padding: 4px;">0 %</td>
                    </tr>
              </tbody>
            </table>
          <h2><strong>L&#x27;avantage supplémentaire : réduction des coûts</strong></h2><p>Bien que notre priorité soit de fournir des performances à faible latence, l&#x27;efficacité du nouveau matériel a également un impact direct et positif sur les coûts des projets Elasticsearch.</p><p>La <a href="https://www.elastic.co/pricing/serverless-search">tarification d&#x27;Elasticsearch Serverless</a> est basée sur l&#x27;utilisation : vous ne payez que pour les ressources d&#x27;ingestion et de recherche que vous consommez. Grâce à un matériel plus récent et plus rapide, vos charges de travail s&#x27;exécuteront souvent avec moins de ressources, ce qui se traduit par une réduction des coûts pour la plupart des projets. Vous bénéficiez ainsi de performances optimales sans le surcoût : l&#x27;efficacité par excellence.</p><h2><strong>Qu&#x27;est-ce que cela signifie pour vous, le développeur ?</strong></h2><p>Cette mise à niveau de l&#x27;infrastructure est entièrement gérée par Elastic ; vous n&#x27;avez donc rien à faire, aucune migration ni modification de configuration. L&#x27;amélioration est immédiate et automatique pour tous vos projets sans serveur sur AWS.</p><p>Cette mise à niveau vous permet de :</p><ul><li><strong>Créez des applications plus rapides</strong> : concentrez-vous sur la vélocité des fonctionnalités, en sachant que votre plateforme de recherche sous-jacente offre la vitesse exigée par vos utilisateurs.</li><li><strong>Innovez en confiance</strong> : déployez de nouvelles fonctionnalités de recherche, d&#x27;observabilité et de sécurité, y compris des capacités d&#x27;IA complexes telles que la recherche vectorielle et le classement par pertinence, sachant que la plateforme peut gérer la charge à des performances optimales.</li><li><strong>Simplifiez votre stack</strong> : utilisez un service entièrement géré qui prend en charge l&#x27;infrastructure, la planification de la capacité et le scaling, et concentrez‑vous sur votre code et vos données.<br/></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Créer des agents avec supervision humaine à l’aide de LangGraph et Elasticsearch]]></title>
            <description><![CDATA[Découvrez comment concevoir des agents avec supervision humaine grâce à LangGraph et Elasticsearch, en intégrant l’humain dans la boucle pour combler les lacunes contextuelles et valider les appels d’outils avant leur exécution.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/human-in-the-loop-agents-langgraph-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/human-in-the-loop-agents-langgraph-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Mon, 05 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/69634710b14adff494b51b2d57d119196ea6d644-838x532.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans cet article, nous allons explorer comment combiner <a href="https://langchain-ai.github.io/langgraph/">LangGraph</a> et Elasticsearch pour créer une application intégrant une supervision humaine (HITL). Cette approche permet aux systèmes d’IA d’impliquer directement les utilisateurs dans le processus décisionnel, rendant les interactions plus fiables et contextualisées. Nous allons illustrer cela à travers un exemple concret, en nous appuyant sur un scénario orienté contexte pour montrer comment les workflows LangGraph peuvent s’intégrer à Elasticsearch pour récupérer des données, traiter les saisies utilisateur et produire des résultats affinés.</p><h2>Conditions</h2><ul><li>NodeJS version 18 ou plus récente</li><li>Clé API OpenAI</li><li>Déploiement Elasticsearch 8.x+</li></ul><h2>Pourquoi utiliser LangGraph pour des systèmes HITL en production</h2><p>Dans un <a href="https://www.elastic.co/search-labs/blog/ai-agent-workflow-finance-langgraph-elasticsearch">article précédent</a>, nous avons présenté LangGraph et ses avantages pour construire un système RAG à l’aide de LLM et d’<a href="https://docs.langchain.com/oss/javascript/langgraph/graph-api#conditional-edges">arêtes conditionnelles</a> permettant de prendre automatiquement des décisions et d’afficher les résultats. Parfois, on ne souhaite pas que le système fonctionne de manière totalement autonome : on préfère que les utilisateurs puissent faire des choix et prendre des décisions dans la boucle d’exécution. C’est ce qu’on appelle la « <strong><em>supervision humaine</em></strong> ».</p><h2>Supervision humaine ou humain dans la boucle</h2><p>Il s’agit d’un concept d’IA qui permet à une personne réelle d’interagir avec des systèmes d’IA pour enrichir le contexte, évaluer ou modifier les réponses, demander des précisions, etc. Ce mécanisme est particulièrement utile dans des contextes à faible tolérance à l’erreur, comme la conformité, la prise de décision ou la génération de contenu, car il renforce la fiabilité des résultats produits par les modèles de langage (LLM).</p><p>Un exemple courant est celui d’un assistant de développement qui demande votre autorisation avant d’exécuter une commande dans le terminal, ou vous présente son raisonnement pas à pas avant de commencer à coder.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4df9fe7383f0dc665a1b0866651f4f6a40a14aa8-499x215.png" style="width: 100%;" /><h2>Elasticsearch + LangGraph : Comment ils interagissent</h2><p><a href="https://www.langchain.com/">LangChain</a> permet d’utiliser Elasticsearch comme <a href="https://js.langchain.com/docs/integrations/vectorstores/elasticsearch/">magasin vectoriel</a> et d’effectuer des requêtes dans des applications LangGraph – ce qui est particulièrement utile pour exécuter des recherches en texte intégral ou sémantiques –, tandis que LangGraph permet de définir le workflow, les outils et les interactions spécifiques. Cette architecture ajoute également la supervision humaine en tant que couche d’interaction supplémentaire avec l’utilisateur.</p><h2>Mise en pratique : supervision humaine dans la boucle</h2><p>Prenons l’exemple d’un avocat qui se pose une question sur une affaire qu’il vient d’accepter. Sans les bons outils, il devrait parcourir manuellement les textes juridiques et les précédents, les lire en intégralité, puis interpréter leur pertinence par rapport à son cas. Avec LangGraph et Elasticsearch, on peut créer un système capable d’interroger une base de données de précédents juridiques et de produire une analyse adaptée au cas en intégrant les détails et le contexte fournis par l’avocat.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e95bf4ce115cbe9954ee5033776408bed485b8d2-1600x906.png" style="width: 100%;" /><p>Le workflow démarre lorsque l’avocat soumet une question juridique. Le système effectue une recherche vectorielle dans Elasticsearch, identifie les précédents les plus pertinents et les présente à l’avocat sous forme de texte en langage naturel. Une fois les documents sélectionnés, le modèle de langage génère une première analyse et vérifie si les informations sont complètes. À ce stade, deux chemins sont possibles : si tout est clair, le système génère directement une analyse finale ; sinon, il interrompt le processus pour demander des précisions à l’avocat. Une fois les informations manquantes ajoutées, le système complète l’analyse en tenant compte des précisions apportées.</p><p>Voici un graphique généré par LangGraph qui illustre le fonctionnement de l’application une fois finalisée. Chaque nœud représente un outil ou une fonctionnalité :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fcdc0dd53c436afc8e575d1f8fd3705b4dfe9a32-262x753.png" style="width: 100%;" /><h3>Ensemble de données</h3><p>Voici l’<a href="https://github.com/Delacrobix/LangGraph-JS-Elasticsearch-human-in-the-loop/blob/main/dataset.json">ensemble de données</a> utilisé dans cet exemple. Cet ensemble de données regroupe plusieurs précédents juridiques, chacun décrivant un cas de retard de service, les motifs invoqués par la cour et la décision finale.</p><pre><code>[
  {
    "pageContent": "Legal precedent: Case B - Service delay not considered breach. A consulting contract used term 'timely delivery' without specific dates. A three-week delay occurred but contract lacked explicit schedule. Court ruled no breach as parties had not defined concrete timeline and delay did not cause demonstrable harm.",
    "metadata": {
      "caseId": "CASE-B-2022",
      "contractType": "consulting agreement",
      "delayPeriod": "three weeks",
      "outcome": "no breach found",
      "reasoning": "no explicit deadline defined, no demonstrable harm",
      "keyTerms": "timely delivery, open terms, schedule definition",
      "title": "Case B: Delay Without Explicit Schedule"
    }
  },
  ...
]</code></pre><h3>Ingestion et configuration de l&#x27;index</h3><p>La configuration de l’index et la logique d’ingestion des données sont définies dans le fichier <a href="https://github.com/Delacrobix/LangGraph-JS-Elasticsearch-human-in-the-loop/blob/main/dataIngestion.ts">dataIngestion.ts</a>, dans lequel on déclare les fonctions de création de l’index. Cette configuration est compatible avec l’interface de <a href="https://python.langchain.com/docs/integrations/vectorstores/elasticsearch/">magasin vectoriel LangChain pour Elasticsearch</a>.</p><p><em>Remarque : la configuration du mapping est également incluse dans le fichier <a href="https://github.com/Delacrobix/LangGraph-JS-Elasticsearch-human-in-the-loop/blob/main/dataIngestion.ts">dataIngestion.ts</a></em>.</p><h3>Installation des packages et configuration des variables d’environnement</h3><p>Initialisons un projet <a href="https://nodejs.org/en">Node.js</a> avec les paramètres par défaut :</p><ul><li><strong>@elastic/elasticsearch : </strong>client Elasticsearch pour Node.js Utilisé pour se connecter, créer des index et exécuter des requêtes.</li><li><strong>@langchain/community :</strong> propose des intégrations avec des outils soutenus par la communauté, y compris le magasin ElasticVectorSearch.</li><li><strong>@langchain/core :</strong> composants de base de LangChain, tels que les chaînes, prompts et utilitaires.</li><li><strong>@langchain/langgraph :</strong> permet l’orchestration basée sur des graphes, avec des workflows reposant sur des nœuds, des arêtes et une gestion d’état.</li><li><strong>@langchain/openai : </strong>fournit un accès aux modèles OpenAI (LLM et embeddings) via LangChain.</li><li><strong>dotenv :</strong> charge les variables d’environnement à partir d’un fichier .env dans process.env. fichier dans process.env.</li><li><strong>tsx : </strong>outil pratique pour exécuter du code TypeScript.</li></ul><p>Exécutez la commande suivante dans la console pour les installer tous :</p><pre><code>npm install @elastic/elasticsearch @langchain/community @langchain/core @langchain/langgraph @langchain/openai dotenv --legacy-peer-deps && npm install --save-dev tsx</code></pre><p>Créez un fichier <code>.env</code> pour configurer les variables d’environnement :</p><pre><code>ELASTICSEARCH_ENDPOINT=
ELASTICSEARCH_API_KEY=
OPENAI_API_KEY=</code></pre><p>Nous utiliserons <a href="https://www.typescriptlang.org/">TypeScript</a> pour écrire le code, car il offre une meilleure sécurité de typage et une expérience développeur plus fiable. Créez un fichier TypeScript nommé <a href="https://github.com/Delacrobix/LangGraph-JS-Elasticsearch-human-in-the-loop/blob/main/main.ts">main.ts</a> et insérez-y le code présenté dans la section suivante.</p><h3>Importation des packages</h3><p>Dans le fichier <a href="https://github.com/Delacrobix/LangGraph-JS-Elasticsearch-human-in-the-loop/blob/main/main.ts">main.ts</a>, nous commençons par importer les modules nécessaires et initialiser la configuration des variables d’environnement. Cela inclut les composants essentiels de LangGraph, les intégrations de modèles OpenAI et le client Elasticsearch.</p><p>Nous importons également les éléments suivants depuis le fichier <a href="https://github.com/Delacrobix/LangGraph-JS-Elasticsearch-human-in-the-loop/blob/main/dataIngestion.ts">dataIngestion.ts</a> :</p><ul><li><strong>ingestData : </strong>fonction qui crée l’index et ingère les données.</li><li><strong>Document et DocumentMetadata :</strong> interfaces définissant la structure des documents de l’ensemble de données.</li></ul><h3>Client du magasin vectoriel Elasticsearch, client d’embeddings et client OpenAI</h3><p>Ce code initialise le magasin vectoriel, le client d’embeddings et un client OpenAI.</p><pre><code>const VECTOR_INDEX = "legal-precedents";

const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
const embeddings = new OpenAIEmbeddings({
  model: "text-embedding-3-small",
});

const esClient = new Client({
  node: process.env.ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: process.env.ELASTICSEARCH_API_KEY ?? "",
  },
});

const vectorStore = new ElasticVectorSearch(embeddings, {
  client: esClient,
  indexName: VECTOR_INDEX,
});</code></pre><p>Le schéma d’état du workflow applicatif facilite la communication entre les nœuds :</p><pre><code>const LegalResearchState = Annotation.Root({
  query: Annotation<string>(),
  analyzedConcepts: Annotation<string[]>(),
  precedents: Annotation<Document[]>(),
  selectedPrecedent: Annotation<Document | null>(),
  draftAnalysis: Annotation<string>(),
  ambiguityDetected: Annotation<boolean>(),
  userClarification: Annotation<string>(),
  finalAnalysis: Annotation<string>(),
});</code></pre><p>L’objet d’état fera transiter, via les nœuds, la requête de l’utilisateur, les concepts extraits, les précédents juridiques identifiés et les éventuelles zones d’ambiguïté. Il conserve également une trace du précédent choisi par l’utilisateur, de l’analyse préliminaire générée en cours de route et de l’analyse finale, une fois toutes les clarifications apportées.</p><h3>Nœuds</h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9bda47908dc0a73fdda222c8ba1382a1ef9dc77a-304x300.png" style="width: 100%;" /><p><strong>searchPrecedents : </strong>ce nœud effectue une recherche de similarité dans le magasin vectoriel d’Elasticsearch, en se basant sur les données saisies par l’utilisateur. Il récupère jusqu’à 5 documents pertinents et les affiche afin qu’ils puissent être examinés par l’utilisateur.</p><pre><code>async function searchPrecedents(state: typeof LegalResearchState.State) {
  console.log(
    "📚 Searching for relevant legal precedents with query:\n",
    state.query
  );

  const results = await vectorStore.similaritySearch(state.query, 5);
  const precedents = results.map((d) => d as Document);

  console.log(`Found ${precedents.length} relevant precedents:\n`);

  for (let i = 0; i < precedents.length; i++) {
    const p = precedents[i];
    const m = p.metadata;
    console.log(
      `${i + 1}. ${m.title} (${m.caseId})\n` +
        `   Type: ${m.contractType}\n` +
        `   Outcome: ${m.outcome}\n` +
        `   Key reasoning: ${m.reasoning}\n` +
        `   Delay period: ${m.delayPeriod}\n`
    );
  }

  return { precedents };
}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/82afb46c4373b01daf7640a528c2ea54db0837b5-304x300.png" style="width: 100%;" /><p><strong>precedentSelection :</strong> ce nœud permet à l’utilisateur de sélectionner, en langage naturel, le cas d’usage correspondant le mieux à sa requête parmi ceux trouvés par la recherche par similarité. À ce stade, l’application <strong>interrompt le workflow</strong> et attend une entrée utilisateur.</p><pre><code>function precedentSelection(state: typeof LegalResearchState.State) {
  console.log("\n⚖️  HITL #1: Human input needed\n");
  const question = "👨‍⚖️  Which precedent is most similar to your case? ";
  const userChoice = interrupt({ question });

  return { userChoice };
}</code></pre><p><strong>selectPrécédent :</strong> ce nœud transmet la saisie de l’utilisateur ainsi que les documents récupérés, afin d’en interpréter le contenu et d’en sélectionner un. Le LLM accomplit cette tâche en renvoyant un numéro correspondant au document qu’il estime le plus pertinent à partir de la saisie en langage naturel.</p><pre><code>async function selectPrecedent(state: typeof LegalResearchState.State) {
  const precedents = state.precedents || [];
  const userInput = (state as any).userChoice || "";

  const precedentsList = precedents
    .map((p, i) => {
      const m = p.metadata;
      return `${i + 1}. ${m.caseId}: ${m.title} - ${m.outcome}`;
    })
    .join("\n");

  const structuredLlm = llm.withStructuredOutput({
    name: "precedent_selection",
    schema: {
      type: "object",
      properties: {
        selected_number: {
          type: "number",
          description:
            "The precedent number selected by the lawyer (1-based index)",
          minimum: 1,
          maximum: precedents.length,
        },
      },
      required: ["selected_number"],
    },
  });

  const prompt = `
    The lawyer said: "${userInput}"

    Available precedents:
    ${precedentsList}

    Which precedent number (1-${precedents.length}) matches their selection?
  `;

  const response = await structuredLlm.invoke([
    {
      role: "system",
      content:
        "You are an assistant that interprets lawyer's selection and returns the corresponding precedent number.",
    },
    { role: "user", content: prompt },
  ]);

  const selectedIndex = response.selected_number - 1;
  const selectedPrecedent = precedents[selectedIndex] || precedents[0];

  console.log(`✅ Selected: ${selectedPrecedent.metadata.title}\n`);
  return { selectedPrecedent };
}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/714e840cc8dab48c436cd066a6fdb4b7dceccf13-428x260.png" style="width: 100%;" /><p><strong>createDraft : </strong>ce nœud génère une première analyse juridique à partir du précédent choisi par l’utilisateur Le modèle LLM évalue dans quelle mesure le précédent s’applique à la question posée par l’avocat et détermine si les informations disponibles sont suffisantes pour continuer.</p><p>Si le précédent est directement applicable, le nœud produit une analyse préliminaire et, en suivant la branche de droite, passe au nœud final. Si le LLM détecte des ambiguïtés – termes contractuels non définis, détails temporels manquants ou conditions floues –, il signale qu’une clarification est nécessaire et fournit la liste des informations à compléter. Dans ce cas, l’ambiguïté déclenche la branche gauche du graphe.</p><pre><code>async function createDraft(state: typeof LegalResearchState.State) {
  console.log("📝 Drafting initial legal analysis...\n");

  const precedent = state.selectedPrecedent;
  if (!precedent) return { draftAnalysis: "" };

  const m = precedent.metadata;

  const structuredLlm = llm.withStructuredOutput({
    name: "draft_analysis",
    schema: {
      type: "object",
      properties: {
        needs_clarification: {
          type: "boolean",
          description:
            "Whether the analysis requires clarification about contract terms or context",
        },
        analysis_text: {
          type: "string",
          description: "The draft legal analysis or the ambiguity explanation",
        },
        missing_information: {
          type: "array",
          items: { type: "string" },
          description:
            "List of specific information needed if clarification is required (empty if no clarification needed)",
        },
      },
      required: ["needs_clarification", "analysis_text", "missing_information"],
    },
  });

  const prompt = `
    Based on this precedent:
    Case: ${m.title}
    Outcome: ${m.outcome}
    Reasoning: ${m.reasoning}
    Key terms: ${m.keyTerms}

    And the lawyer's question: "${state.query}"

    Draft a legal analysis applying this precedent to the question.
    
    If you need more context about the specific contract terms, timeline details, 
    or other critical information to provide accurate analysis, set needs_clarification 
    to true and list what information is missing.
    
    Otherwise, provide the legal analysis directly.
  `;

  const response = await structuredLlm.invoke([
    {
      role: "system",
      content:
        "You are a legal research assistant that analyzes cases and identifies when additional context is needed.",
    },
    { role: "user", content: prompt },
  ]);

  let displayText: string;
  if (response.needs_clarification) {
    const missingInfoList = response.missing_information
      .map((info: string, i: number) => `${i + 1}. ${info}`)
      .join("\n");
    displayText = `AMBIGUITY DETECTED:\n${response.analysis_text}\n\nMissing information:\n${missingInfoList}`;
  } else {
    displayText = `ANALYSIS:\n${response.analysis_text}`;
  }

  console.log(displayText + "\n");

  return {
    draftAnalysis: displayText,
    ambiguityDetected: response.needs_clarification,
  };
}</code></pre><p>Voici les deux chemins que le graphe peut suivre :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/72dda52e1d84818c9f98437f2fa6ecb8e5b2f4ff-438x638.png" style="width: 100%;" /><p>La branche gauche comprend un nœud supplémentaire chargé de gérer la clarification.</p><p><strong>requestClarification :</strong> ce nœud active la seconde étape de supervision humaine lorsque le système estime que l’analyse préliminaire manque de contexte essentiel. Le workflow est interrompu et l’utilisateur est invité à préciser les éléments contractuels manquants détectés par le nœud précédent.</p><pre><code>function requestClarification(state: typeof LegalResearchState.State) {
  console.log("\n⚖️  HITL #2: Additional context needed\n");
  const userClarification = interrupt({
    question: "👨‍⚖️  Please provide clarification about your contract terms:",
  });
  return { userClarification };
}</code></pre><p><strong>generateFinalAnalysis : </strong>ce nœud génère l’analyse juridique finale en combinant le précédent sélectionné avec les informations supplémentaires fournies par l’utilisateur, si nécessaire. Grâce aux clarifications obtenues lors de l’étape précédente de supervision humaine, le LLM synthétise le raisonnement juridique, les éléments contractuels fournis par l’utilisateur et les conditions permettant d’établir s’il y a eu violation.</p><p>Le nœud final fournit une analyse complète, intégrant l’interprétation juridique et des recommandations concrètes.</p><pre><code>async function generateFinalAnalysis(state: typeof LegalResearchState.State) {
  console.log("📋 Generating final legal analysis...\n");

  const precedent = state.selectedPrecedent;
  if (!precedent) return { finalAnalysis: "" };

  const m = precedent.metadata;

  const prompt = `
    Original question: "${state.query}"
    
    Selected precedent: ${m.title}
    Outcome: ${m.outcome}
    Reasoning: ${m.reasoning}
    
    Lawyer's clarification: "${state.userClarification}"
    
    Provide a comprehensive legal analysis integrating:
    1. The selected precedent's reasoning
    2. The lawyer's specific contract context
    3. Conditions for breach vs. no breach
    4. Practical recommendations
  `;

  const response = await llm.invoke([
    {
      role: "system",
      content:
        "You are a legal research assistant providing comprehensive analysis.",
    },
    { role: "user", content: prompt },
  ]);

  const finalAnalysis = response.content as string;

  console.log(
    "\n" +
      "=".repeat(80) +
      "\n" +
      "⚖️  FINAL LEGAL ANALYSIS\n" +
      "=".repeat(80) +
      "\n\n" +
      finalAnalysis +
      "\n\n" +
      "=".repeat(80) +
      "\n"
  );

  return { finalAnalysis };
}</code></pre><p>Construction du graphe :</p><pre><code>const workflow = new StateGraph(LegalResearchState)
  .addNode("analyzeQuery", analyzeQuery)
  .addNode("searchPrecedents", searchPrecedents)
  .addNode("precedentSelection", precedentSelection)
  .addNode("selectPrecedent", selectPrecedent)
  .addNode("createDraft", createDraft)
  .addNode("requestClarification", requestClarification)
  .addNode("generateFinalAnalysis", generateFinalAnalysis)
  .addEdge("__start__", "analyzeQuery")
  .addEdge("analyzeQuery", "searchPrecedents")
  .addEdge("searchPrecedents", "precedentSelection") // HITL #1
  .addEdge("precedentSelection", "selectPrecedent")
  .addEdge("selectPrecedent", "createDraft")
  .addConditionalEdges(
    "createDraft",
    (state: typeof LegalResearchState.State) => {
      // If ambiguity detected, request clarification (HITL #2)
      if (state.ambiguityDetected) return "needsClarification";
      // Otherwise, generate final analysis
      return "final";
    },
    {
      needsClarification: "requestClarification",
      final: "generateFinalAnalysis",
    }
  )
  .addEdge("requestClarification", "generateFinalAnalysis") // HITL #2
  .addEdge("generateFinalAnalysis", "__end__");</code></pre><p>Dans le graphe, on observe que l’arête conditionnelle définit le critère permettant de choisir la branche « finale ». Comme on le voit, la décision repose désormais sur la détection ou non d’ambiguïtés dans l’analyse préliminaire, nécessitant des clarifications supplémentaires.</p><p>Mise en œuvre de l’ensemble :</p><pre><code>await ingestData();

// Compile workflow
const app = workflow.compile({ checkpointer: new MemorySaver() });
const config = { configurable: { thread_id: "hitl-circular-thread" } };

await saveGraphImage(app);

// Execute workflow
const legalQuestion =
    "Does a pattern of repeated delays constitute breach even if each individual delay is minor?"; 

console.log(`⚖️  LEGAL QUESTION: "${legalQuestion}"\n`);

let currentState = await app.invoke({ query: legalQuestion }, config);

// Handle all interruptions in a loop
while ((currentState as any).__interrupt__?.length > 0) {
  console.log("\n💭 APPLICATION PAUSED WAITING FOR USER INPUT...");

  const interruptQuestion = (currentState as any).__interrupt__[0]?.value
    ?.question;
  const userChoice = await getUserInput(
    interruptQuestion || "👤 YOUR CHOICE: "
  );

  currentState = await app.invoke(
    new Command({ resume: userChoice }),
    config
  );
}</code></pre><h3>Exécutez le script :</h3><p>Tout étant prêt, exécutez le fichier <code>main.ts</code> en saisissant la commande suivante dans le terminal :</p><pre><code>tsx main.ts</code></pre><p>Une fois le script lancé, la question <strong>« Une série de retards répétés constitue-t-elle une violation, même si chaque retard est mineur ? »</strong> est envoyée à Elasticsearch pour une recherche par similarité. Les résultats extraits de l’index s’affichent ensuite. L’application détecte que plusieurs précédents juridiques pertinents correspondent à la requête. Elle suspend donc l’exécution et sollicite l’utilisateur pour identifier le précédent le plus approprié :</p><pre><code>📚 Searching for relevant legal precedents with query:
 Does a pattern of repeated delays constitute breach even if each individual delay is minor?
Found 5 relevant precedents:

1. Case H: Pattern of Repeated Delays (CASE-H-2021)
   Type: ongoing service agreement
   Outcome: breach found
   Key reasoning: pattern demonstrated failure to perform, cumulative effect
   Delay period: multiple instances

2. Case E: Minor Delay Quality Maintained (CASE-E-2022)
   Type: service agreement
   Outcome: minor breach only
   Key reasoning: delay minimal, quality maintained, termination unjustified
   Delay period: five days

3. Case A: Delay Breach with Operational Impact (CASE-A-2023)
   Type: service agreement
   Outcome: breach found
   Key reasoning: delay affected operations and caused financial harm
   Delay period: two weeks

4. Case B: Delay Without Explicit Schedule (CASE-B-2022)
   Type: consulting agreement
   Outcome: no breach found
   Key reasoning: no explicit deadline defined, no demonstrable harm
   Delay period: three weeks

5. Case C: Justified Delay External Factors (CASE-C-2023)
   Type: construction service
   Outcome: no breach found
   Key reasoning: external factors beyond control, force majeure applied
   Delay period: one month

⚖️  HITL #1: Human input needed

💭 APPLICATION PAUSED WAITING FOR USER INPUT...
👨‍⚖️  Which precedent is most similar to your case? </code></pre><p>Ce qui rend cette application intéressante, c’est qu’il est possible d’utiliser le langage naturel pour faire un choix, le LLM interprétant l’entrée utilisateur pour identifier la bonne option. Voyons ce qui se passe si l’on saisit <strong>« Case H »</strong></p><pre><code>💭 APPLICATION PAUSED WAITING FOR USER INPUT...
👨‍⚖️  Which precedent is most similar to your case? Case H

✅ Selected: Case H: Pattern of Repeated Delays

📝 Drafting initial legal analysis...

AMBIGUITY DETECTED:
Based on Case H, a pattern of repeated delays can indeed constitute a breach of contract, even if each individual delay is minor. The outcome in Case H indicates that the cumulative effect of these minor delays led to a significant failure to perform the contractual obligations adequately. The reasoning emphasizes that consistent performance is critical in fulfilling the terms of a contract. Therefore, if the repeated delays create a situation where the overall performance is hindered, this pattern could be interpreted as a breach. However, the interpretation may depend on the specific terms of the contract at issue, as well as the expectations of performance set forth in that contract.

Missing information:
1. Specific contract terms regarding performance timelines
2. Details on the individual delays (duration, frequency)
3. Context on consequences of delays stated in the contract
4. Other parties' expectations or agreements related to performance


⚖️  HITL #2: Additional context needed


💭 APPLICATION PAUSED WAITING FOR USER INPUT...
👨‍⚖️  Please provide clarification about your contract terms:</code></pre><p>Le modèle intègre les précisions fournies par l’utilisateur dans le workflow, puis poursuit avec l’analyse finale dès que le contexte est jugé suffisant. À ce stade, le système exploite également l’ambiguïté identifiée plus tôt : l’analyse préliminaire avait révélé des éléments contractuels manquants, susceptibles d’influencer l’interprétation juridique. Ces éléments « manquants » guident le modèle pour identifier les clarifications indispensables à lever les incertitudes et formuler une analyse finale fiable.</p><p>L’utilisateur doit fournir les précisions demandées dans la saisie suivante. Essayons avec : <strong>« Le contrat exige une “livraison rapide” sans calendrier. Huit retards de 2 à 4 jours sur six mois. 50 000 $ de pertes dues à trois délais clients non respectés. Le fournisseur a été prévenu, mais le schéma s’est répété. »</strong></p><pre><code>💭 APPLICATION PAUSED WAITING FOR USER INPUT...
👨‍⚖️  Please provide clarification about your contract terms: Contract requires "prompt delivery" without timelines. 8 delays of 2-4 days over 6 months. $50K in losses from 3 missed client deadlines. Vendor notified but pattern continued.

📋 Generating final legal analysis...

================================================================================
⚖️  FINAL LEGAL ANALYSIS
================================================================================

To analyze the question of whether a pattern of repeated minor delays constitutes a breach of contract, we need to combine insights from the selected precedent, the specifics of the lawyer's contract situation, conditions that typically govern breach versus non-breach, and practical recommendations for the lawyer moving forward.

### 1. Selected Precedent's Reasoning

The precedent case, referred to as Case H, found that a pattern of repeated delays amounted to a breach of contract. The court reasoned that even minor individual delays, when considered cumulatively, demonstrated a failure to perform as stipulated in the contract. The underlying rationale was that the cumulative effect of these minor delays could significantly undermine the purpose of the contract, which typically aims for timely performance and reliable delivery.

### 2. Lawyer's Specific Contract Context

In the lawyer's situation, the contract specified "prompt delivery" but did not provide a strict timeline. The vendor experienced 8 delays ranging from 2 to 4 days over a period of 6 months. These delays culminated in $50,000 in losses due to three missed client deadlines. The vendor was notified regarding these delays; however, the pattern of delays persisted.

Key considerations include:
- **Nature of the Obligations**: While “prompt delivery” does not define a strict timeline, it does imply an expectation for timely performance.
- **Material Impact**: The missed client deadlines indicate that these delays had a material adverse effect on the lawyer's ability to fulfill contractual obligations to third parties, likely triggering damages.

### 3. Conditions for Breach vs. No Breach

**Conditions for Breach**:
- **Pattern and Cumulative Effect**: Similar to the reasoning in Case H, evidence of a habitual pattern of delays can amount to a breach. Even if individual delays are minor, when combined, they may show a lack of diligence or reliability by the vendor.
- **Materiality**: The impact of these delays is crucial. If the cumulative delays adversely affect the contract's purpose or cause significant losses, this reinforces the case for a breach.
- **Notification and Opportunity to Cure**: The fact that the vendor was notified of the delays and failed to rectify the behavior can often be interpreted as a further indication of breach.

**Conditions for No Breach**:
- **Non-Material Delays**: If the delays did not affect the overall contractual performance or client obligations, this may lessen the likelihood of establishing a breach. However, given the risks and losses involved, this seems less relevant in this scenario.
- **Force Majeure or Justifiable Delays**: If the vendor could show that these delays were due to justify circumstances not within their control, it may potentially provide a defense against breach claims.

### 4. Practical Recommendations

1. **Assess Damages**: Document the exact nature of the financial losses incurred due to the missed deadlines to substantiate claims of damages.
  
2. **Gather Evidence**: Collect all communication regarding the delays, including any notifications sent to the vendor about the issues.

3. **Consider Breach of Contract Action**: Based on the precedent and accumulated delays, consider formalized communication to the vendor regarding a breach of contract claim, highlighting both the pattern and the impact of these repeated delays.

4. **Evaluate Remedies**: Depending upon the contract specifics, the lawyer may wish to pursue several remedies, including:
   - **Compensatory Damages**: For the financial losses due to missed deadlines.
   - **Specific Performance**: If timely delivery is critical and can still be enforced.
   - **Contract Termination**: Depending on the severity, terminating the contract and seeking replacements may be warranted.

5. **Negotiate Terms**: If continuing to work with the current vendor is strategic, the lawyer should consider renegotiating terms for performance guarantees or penalties for further delays.

6. **Future Contracts**: In future contracts, consider including explicit timelines and conditions for prompt delivery, as well as specified damages for delays to better safeguard against this issue.

By integrating the legal principles from the precedent with the specific context and conditions outlined, the lawyer can formulate a solid plan to address the repeated delays by the vendor effectively.</code></pre><p>Ce résultat correspond à la dernière étape du workflow : le modèle y intègre le précédent sélectionné (Case H) ainsi que les précisions de l’avocat pour produire une analyse juridique complète. Le système explique pourquoi le schéma de retards observé constitue vraisemblablement une violation, identifie les facteurs à l’appui de cette interprétation et propose des recommandations concrètes. Au final, le résultat montre comment les clarifications liées à la supervision humaine permettent de lever les ambiguïtés et de générer une analyse juridique contextualisée et solide.</p><h3>Autres cas d’usage concrets</h3><p>Ce type d’application, reposant sur Elasticsearch, LangGraph et la supervision humaine, peut également être utile dans d’autres types d’applications, comme :</p><ul><li><strong>Examen préalable des appels d’outils avant leur exécution</strong> : par exemple, en finance, une personne valide les ordres d’achat/vente avant leur passage.</li><li><strong>Ajout de paramètres complémentaires si nécessaire</strong> : par exemple, dans le tri des tickets en support client, où un agent humain choisit la bonne catégorie de problème lorsque l’IA propose plusieurs interprétations possibles.</li></ul><p>Et de nombreux cas d’usage restent à explorer, dans lesquels la supervision humaine pourrait véritablement changer la donne.</p><h2>Conclusion</h2><p>Avec LangGraph et Elasticsearch, il est possible de créer des agents capables de prendre des décisions de manière autonome et de suivre un workflow linéaire, ou d’adapter leur cheminement selon certaines conditions. Avec la supervision humaine, les agents peuvent faire intervenir l’utilisateur dans le processus de décision pour combler les lacunes contextuelles et valider les choix dans des systèmes où la tolérance aux erreurs est critique.</p><p>L’un des atouts de cette approche, c’est qu’elle permet de filtrer un vaste ensemble de données via Elasticsearch, puis d’utiliser un LLM pour extraire un seul document correspondant à la sélection de l’utilisateur. Cette dernière étape serait bien plus complexe avec Elasticsearch seul, car un humain peut exprimer une même intention de recherche de multiples façons en langage naturel.</p><p>Cette approche rend le système plus rapide et économe en jetons, car seul le strict nécessaire est transmis au LLM pour prendre la décision finale, et non l’ensemble du jeu de données. Dans le même temps, cette approche reste très efficace pour détecter l’intention de l’utilisateur et affiner les itérations jusqu’à obtenir l’option souhaitée.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automatisation de l'analyse des logs dans Streams avec le ML]]></title>
            <description><![CDATA[Découvrez comment une approche hybride de ML a atteint une précision de 94 % pour l'analyse syntaxique des logs et 91 % pour le partitionnement des logs grâce à des expériences d’automatisation avec l’empreinte des formats de log dans Streams.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/log-parsing-partitioning-automation-experiments-streams</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/log-parsing-partitioning-automation-experiments-streams</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Nastia Havriushenko]]></dc:creator>
            <pubDate>Fri, 02 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/965c58f19742361160593c38fcaa8b2f4b0d6cc5-3838x2159.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans les piles d&#x27;observabilité modernes, l&#x27;ingestion de logs non structurés provenant de divers fournisseurs de données dans des plateformes comme Elasticsearch reste un défi. La dépendance à des règles de traitement manuellement créées engendre des pipelines fragiles, où même des mises à jour mineures du code en amont entraînent des échecs de traitement et des données non indexées. Cette fragilité est aggravée par le défi de la scalabilité : dans les environnements de microservices dynamiques, l’ajout continu de nouveaux services transforme la maintenance manuelle des règles en un cauchemar opérationnel.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9108ec303339dd091faa3c363c7cf5c228155f49-3840x2160.png" style="width: 100%;" /><p>Notre objectif était de passer à une approche automatisée et adaptative capable de gérer à la fois l’analyse des logs (extraction de champs) et le partitionnement des logs (identification de la source). Nous avons émis l’hypothèse que les grands modèles de langage (LLM), grâce à leur compréhension inhérente de la syntaxe du code et des modèles sémantiques, pourraient automatiser ces tâches avec une intervention humaine minimale.</p><p>Nous sommes heureux d&#x27;annoncer que cette fonctionnalité est déjà disponible dans <a href="http://elastic.co/elasticsearch/streams"><span style="text-decoration:underline">Streams</span></a>.</p><h2>Description de l&#x27;ensemble de données</h2><p>Nous avons choisi une <strong><a href="https://github.com/logpai/loghub">Loghub</a> </strong>collection de logs à des fins de PoC. Pour notre enquête, nous avons sélectionné des échantillons représentatifs issus des domaines clés suivants :</p><ul><li>Systèmes distribués : nous avons utilisé le HDFS (Hadoop Distributed File System) et les ensembles de données Spark. Ils contiennent un mélange de messages d&#x27;information, de débogage et d&#x27;erreur typiques des plateformes de big data.</li><li>Applications serveur et web : les logs des serveurs web Apache et d’OpenSSH ont constitué une source précieuse d’informations sur les accès, les erreurs et les événements liés à la security. Ces éléments sont essentiels pour surveiller le trafic web et détecter les menaces potentielles.</li><li>Systèmes d&#x27;exploitation : nous avons inclus les logs de Linux et Windows. Ces ensembles de données représentent les événements communs et semi-structurés au niveau du système auxquels les équipes d’opérations sont confrontés quotidiennement.</li><li>Systèmes mobiles : pour garantir que notre modèle puisse gérer les logs provenant d&#x27;environnements mobiles, nous avons inclus l&#x27;ensemble de données Android. Ces logs sont souvent volumineux et capturent un large éventail d&#x27;activités au niveau de l&#x27;application et du système sur les appareils mobiles.</li><li>Superordinateurs : pour tester les performances sur des environnements de calcul haute performance (HPC), nous avons intégré l&#x27;ensemble de données BGL (Blue Gene/L), qui présente des logs très structurés avec une terminologie de domaine spécifique.</li></ul><p>L&#x27;un des principaux avantages de la collection Loghub est que les logs sont en grande partie non nettoyés et non étiquetés, reflétant un environnement de production en direct bruyant avec une architecture de microservices.</p><p>Exemples de logs :</p><pre><code>[Sun Dec 04 20:34:21 2005] [notice] jk2_init() Found child 2008 in scoreboard slot 6
[Sun Dec 04 20:34:25 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
[Mon Dec 05 11:06:51 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
17/06/09 20:10:58 INFO output.FileOutputCommitter: Saved output of task 'attempt_201706092018_0024_m_000083_1138' to hdfs://10.10.34.11:9000/pjhe/test/1/_temporary/0/task_201706092018_0024_m_000083
17/06/09 20:10:58 INFO mapred.SparkHadoopMapRedUtil: attempt_201706092018_0024_m_000083_1138: Committed</code></pre><p>De plus, nous avons créé un cluster Kubernetes avec une application web typique et une base de données pour extraire des logs supplémentaires dans le domaine le plus courant.</p><p>Exemple de champs de log communs : horodatage, niveau de log (INFO, AVERTISSEMENT, ERREUR), source, message.</p><h2>Analyse de logs en few-shot avec un LLM</h2><p>Notre premier ensemble d&#x27;expériences s&#x27;est concentré sur une question fondamentale : <strong>Un LLM peut-il identifier de manière fiable les champs clés et générer des règles de parsing cohérentes pour les extraire ?</strong></p><p>Nous avons demandé à un modèle d&#x27;analyser des échantillons de journaux bruts et de générer des règles d&#x27;analyse de journaux sous forme d&#x27;expressions régulières (regex) et de formats <a href="https://www.elastic.co/docs/explore-analyze/scripting/grok">Grok</a>. Nos résultats ont montré que cette approche présente beaucoup de potentiel, mais aussi des défis importants dans sa mise en œuvre.</p><h3>Confiance élevée et conscience du contexte</h3><p>Les premiers résultats étaient prometteurs. Le LLM a démontré une forte capacité à générer des règles d&#x27;analyse qui correspondaient aux exemples fournis avec une grande confiance. Outre la simple correspondance de modèles, le modèle a démontré une capacité de compréhension des logs — il pouvait identifier et nommer correctement la source du log (par exemple, application de suivi de santé, application web Nginx, base de données Mongo).</p><h3>Le dilemme des échantillons d&#x27;entrée « Boucles d&#x27;or »</h3><p>Nos expériences ont rapidement mis en évidence un manque important de robustesse en raison d&#x27;une<strong> sensibilité extrême à l&#x27;échantillon d&#x27;entrée.</strong> Les performances du modèle fluctuent considérablement en fonction des exemples de logs spécifiques inclus dans l&#x27;invite. Nous avons observé un problème de similitude des logs, dans lequel l&#x27;échantillon de log devait inclure <em>juste assez de logs diversifiés </em> :</p><ul><li>Trop homogène (surapprentissage)<strong> :</strong> si les logs d&#x27;entrée sont trop similaires, le LLM a tendance à <strong>surspécifier</strong>. Il traite les données variables — telles que des noms spécifiques de classes Java dans une trace de pile — comme des parties statiques du modèle. Il en résulte des règles fragiles qui ne couvrent qu&#x27;une infime partie des logs et extraient des champs inutilisables.</li><li>Trop hétérogène (confusion) : à l&#x27;inverse, si l&#x27;échantillon contient une variance de formatage significative, ou pire, des « logs poubelles » comme des barres de progression, des tableaux de mémoire ou de l&#x27;art ASCII, le modèle peine à trouver un dénominateur commun. Il en vient souvent à générer des regex complexes et cassés ou à généraliser paresseusement toute la ligne en un seul champ de bloc de message.</li></ul><h3>La contrainte de la fenêtre contextuelle</h3><p>Nous avons également rencontré un goulot d&#x27;étranglement au niveau de la fenêtre contextuelle. Lorsque les logs d&#x27;entrée étaient longs, hétérogènes, ou riches en champs extractibles, la sortie du modèle se détériorait souvent, devenant « désordonnée » ou trop longue pour s&#x27;intégrer dans la fenêtre de contexte de sortie. Bien entendu, le découpage aide dans ce cas. En divisant les logs à l&#x27;aide de délimiteurs basés sur les caractères et sur les entités, nous pourrions aider le modèle à se concentrer sur l&#x27;extraction des champs principaux sans être submergé par le bruit.</p><h3>L&#x27;écart de cohérence et de standardisation</h3><p>Même lorsque le modèle a généré des règles avec succès, nous avons noté de légères incohérences :</p><ul><li>Variantes de dénomination des services : le modèle propose différents noms pour une même entité (par exemple, en étiquetant la source « Spark », « Apache Spark » et « Spark Log Analytics » dans différentes exécutions).</li><li>Variations dans la dénomination des champs : les noms des champs n&#x27;étaient pas normalisés (par exemple, <code>id</code> vs. <code>service.id</code> vs. <code>device.id</code>). Nous avons normalisé les noms en utilisant une <a href="https://www.elastic.co/docs/reference/ecs/ecs-field-reference">nomenclature de champ Elastic standardisée</a>.</li><li>Variance de résolution : la résolution de l&#x27;extraction de champ variait en fonction de la similarité des logs d&#x27;entrée les uns avec les autres.</li></ul><h2>Empreinte de format de log</h2><p>Pour relever le défi de la similarité des logs, nous introduisons une heuristique haute performance : <strong>l&#x27;empreinte de format de log (LFF)</strong>.</p><p>Au lieu d&#x27;introduire des logs bruts et bruyants directement dans un LLM, nous appliquons d&#x27;abord une transformation déterministe pour révéler la structure sous-jacente de chaque message. Cette étape de pré-traitement rend abstraites les données variables, générant une « empreinte » simplifiée qui nous permet de regrouper les logs connexes.</p><p>La logique de mapping est simple pour garantir la vitesse et la cohérence :</p><ol><li>Abstraction des chiffres : toute séquence de chiffres (0-9) est remplacée par un simple ‘0’.</li><li>Abstraction du texte : toute séquence de caractères alphabétiques avec des espaces blancs est remplacée par un seul « a ».</li><li>Normalisation des espaces blancs : toutes les séquences d&#x27;espaces blancs (espaces, tabulations, nouvelles lignes) sont regroupées en un seul espace.</li><li>La préservation des symboles : la ponctuation et les caractères spéciaux (par exemple, :, [, ], /) sont préservés, car ils sont souvent les indicateurs les plus forts de la structure des logs.</li></ol><p>Nous introduisons l&#x27;approche de mapping des logs. Les modèles de mapping de base comprennent les éléments suivants :</p><ul><li>Chiffres 0 à 9, quelle que soit leur longueur, de &gt; à « 0 ».</li><li>Texte (caractères alphabétiques avec espaces) de n&#x27;importe quelle longueur -&gt; à &#x27;a&#x27;.</li><li>Espaces blancs, onglets et nouvelles lignes : &gt; pour un seul espace.</li></ul><p>Examinons un exemple de la manière dont ce mapping nous permet de transformer les logs.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/78fa2887486eb9417804354ee3bf2a4fdb0f6383-846x252.png" style="width: 100%;" /><p>Par conséquent, nous obtenons les masques de log suivants :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ec439a3d3a25002498b97defcff733ea5ebc6b55-826x94.png" style="width: 100%;" /><p>Remarquez les empreintes digitales des deux premiers logs. Malgré des horodatages, des classes de sources et un contenu de message différents, leurs préfixes (<code>0/0/0 0:0:0 a a.a:</code>) sont identiques. Cet alignement structurel nous permet de regrouper automatiquement ces logs dans le même cluster.</p><p>Le troisième log, cependant, produit une empreinte digitale complètement divergente (<code>0-0-0...</code>). Cela nous permet de le séparer algorithmiquement du premier groupe <em>avant même d&#x27;</em> invoquer un LLM.</p><h2>Partie bonus : implémentation instantanée avec ES|QL</h2><p>C&#x27;est aussi simple que de passer cette requête dans Discover.</p><pre><code>FROM loghub |
EVAL pattern = REPLACE(REPLACE(REPLACE(REPLACE(raw_message, "[ \t\n]+", " "), "[A-Za-z]+", "a"), "[0-9]+", "0"), "a( a)+", "a") |
STATS total_count = COUNT(), ratio = COUNT() / 2000.0, datasources=VALUES(filename), example=TOP(raw_message, 3, "desc") BY SUBSTRING(pattern, 0, 15) |
SORT total_count DESC |
LIMIT 100</code></pre><p><strong>Décomposition de la requête :</strong></p><p><strong>FROM</strong> loghub : cible notre index contenant les données de journal brut.</p><p><strong>EVAL</strong> pattern = ... : la logique du mapping de base. Nous enchaînons les fonctions REPLACE pour effectuer l&#x27;abstraction (par exemple, les chiffres en &#x27;0&#x27;, le texte en &#x27;a&#x27;, etc.) et enregistrons le résultat dans un champ &quot;pattern&quot;.</p><p><strong>STATS </strong>[column1 =] expression1, …<strong> BY </strong>SUBSTRING(pattern, 0, 15) :</p><p>Ceci est une étape du clustering. Nous regroupons les logs qui partagent les 15 premiers caractères de leur schéma et créons des champs agrégés tels que le nombre total de logs par groupe, la liste des sources de données des logs, le préfixe du schéma, 3 exemples de logs</p><p><strong>SORT</strong> total_count DESC | <strong>LIMIT</strong> 100 : affiche les 100 schémas de log les plus fréquents</p><p>Les résultats de la requête sur LogHub sont affichés ci-dessous :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b119498f124376c41d242a099bf9081fd6536be8-1600x394.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4dcfc0a5b7fa753497cc5def5ea3cd54449c0481-1600x719.png" style="width: 100%;" /><p>Comme démontré dans la visualisation, cette approche « sans LLM » partitionne les logs avec une grande précision. Elle a réussi à clusterer complètement 10 sources de données sur 16 (basées sur les étiquettes LogHub) (&gt;90 %) et a atteint un clustering majoritaire dans 13 sources sur 16 (&gt;60 %) — le tout sans nécessiter de nettoyage, de prétraitement ou de réglage fin supplémentaire.</p><p>L&#x27;empreinte digitale du format du log offre une alternative pragmatique et à fort impact, ainsi qu&#x27;un complément aux solutions de ML sophistiquées telles que l&#x27;<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-categorize-text-aggregation">analyse du modèle de log</a>. Il offre des informations immédiates sur les relations entre les logs et gère efficacement les grands ensembles de logs.</p><ul><li>Polyvalence en tant que primitif </li></ul><p>Grâce à la mise en œuvre d&#x27;<a href="https://www.elastic.co/blog/getting-started-elasticsearch-query-language">ES|QL</a>, LFF sert à la fois d&#x27;outil autonome pour des diagnostics/visualisations rapides des données, et d&#x27;élément de base dans les pipelines d&#x27;analyse des journaux pour les cas d&#x27;utilisation à grand volume. </p><ul><li>Flexibilité</li></ul><p>LFF est facile à personnaliser et à étendre pour capturer des modèles spécifiques, c&#x27;est-à-dire des nombres hexadécimaux et des adresses IP.</p><ul><li>Stabilité déterministe</li></ul><p>Contrairement aux algorithmes de clustering basés sur le ML, la logique LFF est simple et déterministe. Les nouveaux logs entrants n&#x27;affectent pas rétroactivement les clusters de logs existants.</p><ul><li>Performance et mémoire</li></ul><p>Il nécessite peu de mémoire, pas de formation ni de GPU, ce qui le rend idéal pour les environnements à haut débit et en temps réel.</p><h2>Combiner une empreinte digitale au format log avec un LLM</h2><p>Pour valider l&#x27;architecture hybride proposée, chaque expérience contenait un sous-ensemble aléatoire de 20 % des logs de chaque source de données. Cette contrainte simule un environnement de production réel où les logs sont traités par lots plutôt que comme un bloc monolithique de données historiques.</p><p>L’objectif était de démontrer que le LFF agit comme une couche de compression efficace. Nous avons cherché à prouver que des règles d’analyse à haute couverture pouvaient être générées à partir de petits échantillons sélectionnés et généralisées avec succès à l’ensemble de l’ensemble de données.</p><h2>Pipeline d&#x27;exécution</h2><p>Nous avons mis en œuvre un pipeline à plusieurs étapes qui filtre, regroupe et applique un échantillonnage stratifié aux données avant qu&#x27;elles n&#x27;atteignent le LLM.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b3f46ea471760b406a32fc7d4bc74cc03faaced2-3840x1660.png" style="width: 100%;" /><p>1. clustering hiérarchique en deux étapes</p><ul><li>Sous-classes (correspondance exacte) : les logs sont agrégés par empreintes identiques. Tous les logs d&#x27;une sous-classe partagent exactement la même structure de format.</li><li>Nettoyage des aberrations. Nous écartons toutes les sous-classes qui représentent moins de 5 % du volume total des logs. Cela garantit que le LLM se concentre sur le signal dominant et ne sera pas distrait par le bruit ou les logs malformés.</li><li>Métaclasses (correspondance avec le préfixe) : les sous-classes restantes sont regroupées en métaclasses en fonction des N premiers caractères de l&#x27;empreinte digitale du format. Nous avons choisi N=5 pour le Log parsing et N=15 pour le Log partitioning lorsque les sources de données sont inconnues.</li></ul><p>2. Échantillonnage stratifié. Une fois l&#x27;arbre hiérarchique établit, nous construisons l&#x27;échantillon de log pour le LLM. L&#x27;objectif stratégique est de maximiser la couverture de variance tout en minimisant l&#x27;utilisation des jetons.</p><ul><li>Nous sélectionnons des logs représentatifs de <em>chaque</em> sous-classe valide au sein de la métaclasse plus large.</li><li>Pour gérer un cas limite de sous-classes trop nombreuses, nous appliquons un échantillonnage aléatoire pour s&#x27;adapter à la taille de la fenêtre cible.</li></ul><p>3. Génération de règles Finally, nous demandons au LLM de générer une règle d&#x27;analyse regex qui convient à tous les logs dans l&#x27;échantillon fourni pour chaque Métaclasse. Pour notre PoC, nous avons utilisé le mini-modèle GPT-4o.</p><h2>Résultats expérimentaux et observations</h2><p>Nous avons atteint une précision de parsing de 94 % et une précision de partitionnement de 91 % sur l&#x27;ensemble de données Loghub.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/49b2b6a1401dd1f33951da68e5a3fac37d0b5aaa-1600x1506.png" style="width: 100%;" /><p>La matrice de confusion ci-dessus illustre les résultats du partitionnement des logs. L’axe vertical représente les sources de données réelles, et l’axe horizontal représente les sources de données prédites. L&#x27;intensité de la carte thermique correspond au volume de logs, les vignettes plus claires indiquant un nombre plus élevé. L&#x27;alignement diagonal démontre la haute fidélité du modèle dans l&#x27;attribution des sources, avec un minimum de dispersion.</p><h2>Nos informations sur les benchmarks de performance :</h2><ul><li><strong>Base optimale :</strong> une fenêtre contextuelle de <strong>30 à 40 échantillons de logs</strong> par catégorie s&#x27;est avérée être le « point idéal », produisant régulièrement une analyse syntaxique robuste avec des motifs Regex et Grok.</li><li><strong>Minimisation de l&#x27;entrée :</strong> nous avons réduit la taille de l&#x27;entrée à 10 logs par catégorie pour les motifs Regex et n&#x27;avons observé qu&#x27;une baisse de 2 % des performances d&#x27;analyse, ce qui confirme que l&#x27;échantillonnage basé sur la diversité est plus critique que le volume brut.</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Présentation des modèles Jina, de leurs fonctionnalités et de leurs cas d’usage dans Elasticsearch]]></title>
            <description><![CDATA[Explorez les embeddings multimodaux Jina, Reranker v3 et les modèles d'embedding sémantique, et découvrez comment les utiliser en mode natif dans Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/jina-models-elasticsearch-guide</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/jina-models-elasticsearch-guide</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Scott Martens]]></dc:creator>
            <pubDate>Thu, 01 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/407b4c862b51ebdfc7f26db4e25950a65caf1673-656x442.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Jina, développé par Elastic, propose des modèles fondamentaux pour la recherche, adaptés aux applications et à l’automatisation des processus métier. Ces modèles fournissent des fonctionnalités essentielles pour intégrer l’IA dans des applications Elasticsearch ou des projets d’IA innovants.</p><p>Les modèles Jina se répartissent en trois grandes catégories, conçues pour faciliter le traitement, l’organisation et la recherche d’informations :</p><ul><li>Modèles d’embedding sémantique</li><li>Modèles de reclassification</li><li>Petits modèles de langage génératif</li></ul><h2>Modèles d’embedding sémantique</h2><p>L’idée derrière les embeddings sémantiques est qu’un modèle d’IA peut apprendre à représenter certains aspects du sens d’une entrée en s’appuyant sur la géométrie d’espaces à très grande dimension.</p><p>On peut considérer un embedding sémantique comme un point (techniquement un <em>vecteur</em>) dans un espace à plusieurs dimensions. Un modèle d’embedding est un réseau de neurones qui reçoit des données numériques en entrée (souvent du texte ou une image) et renvoie l’emplacement du point correspondant dans un espace multidimensionnel, sous forme de coordonnées numériques. Si le modèle est efficace, la distance entre deux embeddings sémantiques est proportionnelle à la similarité de sens des objets correspondants.</p><p>Pour comprendre l’intérêt de cette approche dans les applications de recherche, imaginez les embeddings des mots « chien » et « chat » comme des points dans l’espace :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/802cf9bbcb82180d3fc91009f9f62027eee8f031-615x615.png" style="width: 100%;" /><p>Un bon modèle d’embedding générera une représentation du mot « félin » bien plus proche de « chat » que de « chien », tandis que « canidé » sera plus proche de « chien » que de « chat », car ces mots partagent un sens très proche :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bce49daf9a31b8fb7ce1c6ef7ae4e8117a4e8b33-615x615.png" style="width: 100%;" /><p>Si un modèle est multilingue, nous nous attendrions à la même chose pour les traductions de « chat » et « chien » :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ce4d030385324526cbd7539140e0e634d939371c-615x615.png" style="width: 100%;" /><p>Les modèles d’embedding traduisent la similarité ou la différence de sens entre des éléments en relations spatiales entre leurs représentations vectorielles. Les illustrations ci-dessus sont en deux dimensions pour faciliter la visualisation, mais les modèles d’embedding produisent des vecteurs comportant des dizaines, voire des milliers de dimensions. Cela leur permet de capturer les subtilités du sens dans des textes entiers, en leur associant un point dans un espace comportant des centaines, voire des milliers de dimensions, pour des documents pouvant contenir des milliers de mots.</p><h2>Embeddings multimodaux</h2><p>Les modèles multimodaux étendent le principe des embeddings sémantiques à d’autres types de contenu que le texte – notamment les images. On s’attend donc à ce qu’un embedding d’image soit proche de celui d’une description fidèle de cette image :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ac6aef5b1423e5fe4853e8a547a74e66b0885c2-615x615.png" style="width: 100%;" /><p>Les embeddings sémantiques offrent de nombreux cas d’usage. Ils peuvent notamment servir à créer des classificateurs efficaces, à regrouper les données (data clustering), ou encore à effectuer des tâches comme la déduplication ou l’analyse de la diversité des données – des fonctionnalités clés dans les environnements big data où les volumes à traiter sont trop importants pour être gérés manuellement.</p><p>L’usage principal des embeddings concerne la recherche d’informations. Elasticsearch peut stocker des objets de récupération avec des embeddings comme clés. Les requêtes sont converties en vecteurs d’embedding, et une recherche renvoie les objets stockés dont les clés sont les plus proches du vecteur d’embedding de la requête.</p><p>Là où la <em>recherche vectorielle</em> traditionnelle (par vecteur unique ou <em>vecteur clairsemé</em>) utilise des vecteurs basés sur les mots ou les métadonnées dans les documents et les requêtes, la <em>recherche par embeddings</em> (ou <em>vecteurs denses</em>) utilise des significations évaluées par l’IA plutôt que des mots. Cela les rend en général plus flexibles et plus précis que les méthodes de recherche classiques.</p><h2>Apprentissage par représentation de type matriochka</h2><p>Le nombre de dimensions d’un embedding et la précision des valeurs qu’il contient ont un impact significatif sur les performances. Les espaces très dimensionnels et les nombres de très grande précision permettent de représenter des informations très complexes et détaillées, mais nécessitent des modèles d’IA plus coûteux à entraîner et à exécuter. Les vecteurs générés occupent également plus d’espace de stockage et nécessitent davantage de ressources de calcul pour mesurer les distances entre eux. L’utilisation de modèles d’embedding sémantique implique donc un compromis important entre précision et consommation de ressources.</p><p>Pour maximiser la flexibilité côté utilisateur, les modèles Jina sont entraînés avec une technique appelée <a href="https://arxiv.org/abs/2205.13147">Matryoshka Representation Learning</a>. Cette méthode pousse le modèle à prioriser les distinctions sémantiques importantes dans les premières dimensions du vecteur, ce qui permet ensuite de tronquer les dimensions les plus élevées sans perte significative de performance.</p><p>Concrètement, cela signifie que les utilisateurs des modèles Jina peuvent choisir le nombre de dimensions qu’ils souhaitent attribuer à leurs embeddings. Réduire le nombre de dimensions entraîne une perte de précision, mais la dégradation des performances reste mineure. Pour la plupart des tâches, les performances des modèles Jina diminuent d’environ 1 à 2 % chaque fois que l’on réduit la taille des embeddings de 50 %, jusqu’à une réduction d’environ 95 %.</p><h2>Récupération asymétrique</h2><p>La similarité sémantique est généralement mesurée de façon symétrique. La valeur obtenue en comparant « chat » à « chien » est la même que celle obtenue en comparant « chien » à « chat ». Mais lorsqu’on utilise des embeddings pour la recherche d’informations, les résultats sont meilleurs si l’on casse cette symétrie et qu’on encode les requêtes différemment des objets à retrouver.</p><p>Cela tient à la façon dont les modèles d’embedding sont entraînés. Les données d’entraînement contiennent des éléments similaires, comme des mots, dans des contextes variés, et les modèles apprennent à en déduire le sens en comparant les similitudes et les différences contextuelles entre ces éléments.</p><p>Ainsi, il se peut par exemple que le mot « animal » apparaisse rarement dans les mêmes contextes que « chat » ou « chien », et que l’embedding du mot « animal » ne soit donc pas particulièrement proche de ceux de « chat » ou « chien ».</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9a33163405af6c71ee7f4ba8ebc86af39e295a69-615x615.png" style="width: 100%;" /><p>Cela réduit la probabilité qu’une requête sur le mot « animal » retourne des documents traitant de chats et de chiens — ce qui est précisément l’effet inverse de l’objectif recherché. On encode donc le mot « animal » différemment selon qu’il s’agit d’une requête ou d’un objet cible à retrouver :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/363992d4f0affba7937c0c8a9f82c9a531fcd3ba-615x615.png" style="width: 100%;" /><p>La <em>recherche asymétrique</em> consiste à utiliser un modèle différent pour les requêtes, ou à entraîner un modèle d’embedding de façon spécifique pour encoder différemment les éléments selon qu’ils sont stockés pour la recherche ou utilisés comme requêtes.</p><h2>Embeddings multi-vecteurs</h2><p>Les embeddings simples sont efficaces pour la recherche d’informations car ils s’intègrent bien au fonctionnement d’une base indexée : les objets à retrouver sont stockés avec un unique vecteur d’embedding utilisé comme clé de recherche. Lorsque les utilisateurs interrogent le magasin de documents, leurs requêtes sont converties en vecteurs d’embedding, et les documents dont la clé est la plus proche de celle de la requête (dans l’espace vectoriel de haute dimension) sont retournés comme correspondances candidates.</p><p>Les embeddings multi-vecteurs fonctionnent différemment. Au lieu de générer un vecteur de longueur fixe pour représenter une requête ou un objet stocké, ils produisent une séquence d’embeddings représentant des parties plus petites de ces éléments. Ces parties sont généralement des jetons ou mots pour les textes, ou des fragments d’image pour les données visuelles. Ces embeddings traduisent le sens de chaque élément dans son contexte.</p><p>Par exemple, prenons ces phrases:</p><ul><li>She had a heart of gold.</li><li>She had a change of heart.</li><li>She had a heart attack.</li></ul><p>En apparence, ces phrases sont très similaires, mais un modèle multi-vecteur générerait probablement des embeddings très différents pour chaque occurrence du mot « heart », car son sens varie dans le contexte de chaque phrase:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a33e60c8d8ee3d312bca8375ca2a8b0a0cd40ba9-615x615.png" style="width: 100%;" /><p>Comparer deux objets à l’aide de leurs embeddings multi-vecteurs revient souvent à calculer leur distance de Chamfer : on compare chaque élément d’un embedding avec ceux de l’autre, puis on additionne les distances minimales. D’autres systèmes, y compris les modules de reclassification Jina présentés plus bas, transmettent ces données à un modèle d’IA spécifiquement entraîné à évaluer leur similarité. Ces deux approches offrent généralement une précision supérieure à la simple comparaison de vecteurs uniques, car les embeddings multi-vecteurs capturent beaucoup plus d’informations contextuelles.</p><p>Toutefois, les embeddings multivecteurs sont peu adaptés à l’indexation. Ils sont souvent utilisés dans les tâches de reclassification, comme illustré dans le modèle <code>jina-colbert-v2</code> présenté dans la section suivante.</p><h2>Modèles d’embedding Jina</h2><h3>Jina Embeddings v4</h3><p><a href="https://jina.ai/news/jina-embeddings-v4-universal-embeddings-for-multimodal-multilingual-retrieval/"><strong>jina-embeddings-v4</strong></a> est un modèle multilingue et multimodal de 3,8 milliards de paramètres (3,8 × 10⁹), compatible avec des textes dans une grande diversité de langues. Il repose sur une architecture innovante qui exploite les connaissances visuelles et linguistiques pour améliorer les performances dans les deux domaines, en particulier dans les tâches de recherche d’images — notamment la <a href="https://huggingface.co/tasks/visual-document-retrieval">recherche de documents visuels</a>. Cela signifie qu’il est capable de traiter des images comme des graphiques, des présentations, des captures d’écran, des pages scannées ou des schémas — des types d’images courants contenant souvent du texte embarqué, en dehors du champ des modèles de vision entraînés sur des scènes du monde réel.</p><p>Nous avons optimisé ce modèle pour différentes tâches à l’aide d’adaptateurs compacts LoRA (<a href="https://huggingface.co/docs/peft/en/package_reference/lora">Low-Rank Adaptation</a>). Cette approche nous permet d’entraîner un modèle unique capable de se spécialiser dans plusieurs tâches sans perte de performance, pour un coût mémoire ou calculatoire minimal.</p><p>Principales fonctionnalités :</p><ul><li>Des performances de pointe en recherche de documents visuels, avec en plus une excellente prise en charge du texte multilingue et des images classiques — surpassant des modèles bien plus volumineux.</li><li>Prise en charge de contextes d’entrée étendus : 32 768 jetons correspondent à environ 80 pages de texte en anglais double interligne, et 20 mégapixels équivalent à une image de 4 500 × 4 500 pixels.</li><li>Taille des embeddings sélectionnée par l’utilisateur, allant de 2048 dimensions au maximum jusqu’à 128 dimensions. Nous avons constaté empiriquement une forte dégradation des performances en dessous de ce seuil.</li><li>Compatibilité avec les embeddings simples et multi-vecteurs. Pour le texte, la sortie multivecteur se compose d’un embedding de 128 dimensions pour chaque jeton d’entrée. Pour les images, un embedding de 128 dimensions est généré pour chaque bloc de 28 × 28 pixels nécessaires à la couverture de l’image.</li><li>Optimisation pour la recherche asymétrique grâce à une paire d’adaptateurs LoRA spécialement entraînés à cet effet.</li><li>Un adaptateur LoRA optimisé pour le calcul de similarité sémantique.</li><li>Prise en charge spécifique des langages de programmation et des frameworks IT, également via un adaptateur LoRA.</li></ul><p>Nous avons développé <code>jina-embeddings-v4</code> comme outil polyvalent pour toute une gamme de tâches : recherche, compréhension du langage naturel et analyse basée sur l’IA. C’est un modèle relativement compact au vu de ses capacités, mais qui demande tout de même des ressources importantes pour être déployé, et convient mieux à une utilisation via une API cloud ou dans un environnement haute performance.</p><h3>Jina Embeddings v3</h3><p><a href="https://jina.ai/news/jina-embeddings-v3-a-frontier-multilingual-embedding-model/"><strong>jina-embeddings-v3</strong></a> est un modèle d’embedding multilingue, léger, performant, axé sur le texte, avec moins de 600 millions de paramètres. Il prend en charge jusqu’à 8 192 jetons de texte en entrée et génère des embeddings vectoriels simples, avec des tailles personnalisables (de 1 024 à 64 dimensions).</p><p>Nous avons entraîner<code>jina-embeddings-v3</code> non seulement pour la recherche d’informations et la similarité sémantique, mais aussi pour des tâches de classification, comme l’analyse de sentiments, la modération de contenu, le clustering, l’agrégation de nouvelles et la recommandation. Comme <code>jina-embeddings-v4</code>, ce modèle utilise des adaptateurs LoRA spécialisés pour les cas d’usage suivants :</p><ul><li>Récupération asymétrique</li><li>Similarité sémantique</li><li>Classification</li><li>Clustering</li></ul><p><code>jina-embeddings-v3</code> est un modèle beaucoup plus compact que <code>jina-embeddings-v4</code> , avec une taille de contexte en entrée considérablement réduite, mais qui est aussi moins coûteux à exécuter. Malgré cela, il offre des performances très compétitives (bien qu’uniquement sur du texte) et représente un choix pertinent pour de nombreux cas d’usage.</p><h3>Embeddings de code Jina</h3><p>Les modèles Jina spécialisés pour l’embedding de code — <a href="https://jina.ai/models/jina-code-embeddings-1.5b"><strong>jina-code-embeddings</strong></a> (0,5 Md et 1,5 Md de paramètres) — prennent en charge 15 langages de programmation ainsi que des textes en anglais dans le domaine de l’informatique et des technologies de l’information. Ce sont des modèles compacts, avec respectivement 500 millions et 1,5 milliard de paramètres. Les deux modèles acceptent jusqu’à 32 768 jetons en entrée et permettent à l’utilisateur de définir la taille des embeddings générés : de 896 à 64 dimensions pour le plus petit, et de 1 536 à 128 pour le plus grand.</p><p>Ces modèles prennent en charge la recherche asymétrique, pour cinq spécialisations par type de tâche, en utilisant une méthode de <a href="https://arxiv.org/abs/2101.00190">réglage par préfixe</a> plutôt que des adaptateurs LoRA :</p><ul><li><strong>Code vers code.</strong> Récupère du code similaire entre différents langages de programmation. Utilisé pour l’alignement de code, l’élimination des doublons, la migration et le refactoring.</li><li><strong>Langage naturel vers code.</strong> Permet de retrouver du code correspondant à une requête en langage naturel, un commentaire ou une description.</li><li><strong>Code vers langage naturel. </strong>Associe du code source à de la documentation ou à d’autres textes en langage naturel.</li><li><strong>Complétion de code à partir de code.</strong> Suggère du code pertinent pour compléter ou améliorer un extrait existant.</li><li><strong>Questions-réponses techniques.</strong> Fournit des réponses en langage naturel sur des sujets technologiques, idéal pour des cas d’usage liés à l’assistance technique.</li></ul><p>Ces modèles offrent des performances supérieures pour les tâches portant sur la documentation technique et les ressources de développement, à un coût computationnel relativement faible. Ils s’intègrent facilement dans les environnements de développement et les assistants de programmation.</p><h3>Jina ColBERT v2</h3><p><a href="https://jina.ai/models/jina-colbert-v2"><strong>jina-colbert-v2</strong></a> est un modèle d’embedding multivecteur de 560 millions de paramètres. Il est multilingue, entraîné à partir de données couvrant 89 langues, et prend en charge les tailles d’embedding variables et la recherche asymétrique.</p><p>Comme mentionné précédemment, les plongements multivecteurs sont peu adaptés à l’indexation mais sont très utiles pour augmenter la précision des résultats d’autres stratégies de recherche. En utilisant <code>jina-colbert-v2</code><strong>,</strong> vous pouvez calculer à l’avance les embeddings multivecteurs, puis les utiliser pour reclasser les candidats lors de la recherche, au moment de la requête. Cette méthode est moins précise que l’utilisation directe d’un modèle de reclassification, mais beaucoup plus efficace, car elle consiste uniquement à comparer les embeddings multivecteurs déjà stockés, sans faire appel au modèle d’IA à chaque requête ou comparaison de candidats. Elle est particulièrement adaptée aux cas d’usage où la latence et le coût de calcul d’un modèle de reclassification seraient trop élevés, ou lorsque le nombre de documents candidats est trop important.</p><p>Ce modèle produit une séquence d’embeddings, un par jeton d’entrée, et les utilisateurs peuvent choisir des embeddings de 128, 96 ou 64 dimensions. Les correspondances candidates sont limitées à 8 192 jetons. Les requêtes sont encodées de manière asymétrique, ce qui impose à l’utilisateur de spécifier si un texte est une requête ou une correspondance candidate, et de limiter la requête à 32 jetons.</p><h3>Jina CLIP v2</h3><p><a href="https://jina.ai/news/jina-clip-v2-multilingual-multimodal-embeddings-for-text-and-images/"><strong>jina-clip-v2</strong></a> est un modèle d’embedding multimodal de 900 millions de paramètres, entraîné pour produire des embeddings proches entre un texte et une image décrivant le même contenu. Son usage principal est la recherche d’images à partir de requêtes textuelles, mais c’est aussi un modèle textuel performant. Il permet de réduire les coûts liés à la gestion de modèles distincts pour la recherche texte-texte et texte-image.</p><p>Ce modèle prend en charge un contexte d’entrée textuel de 8 192 jetons, et les images sont redimensionnées à 512 × 512 pixels avant génération des embeddings.</p><p>Les architectures CLIP (Contrastive Language–Image Pretraining) sont simples à entraîner, produisent des modèles compacts, mais présentent des limites structurelles importantes. Elles ne permettent pas de transférer la connaissance d’un support à un autre pour améliorer les performances. Elles ne peuvent pas utiliser un support pour améliorer leurs performances sur un autre. Ainsi, même si le modèle sait que les mots « chien » et « chat » sont plus proches en sens que ne l’est « voiture », il ne saura pas forcément qu’une image de chien est plus proche d’une image de chat qu’elle ne l’est d’une image de voiture.</p><p>Cependant, ce modèle souffre également d’un problème connu sous le nom de <em>décalage de modalité</em> : par exemple, un texte sur les chiens pourrait être plus proche, en termes d’embedding, d’un texte sur les chats que d’une image de chien. À cause de cette limitation, nous recommandons d’utiliser CLIP soit pour la recherche texte-image, soit comme modèle textuel seul, mais pas pour combiner les deux dans une même requête.</p><h2>Modèles de reclassification</h2><p>Les modèles de reclassification prennent une ou plusieurs correspondances candidates, ainsi qu’une requête en entrée, et les comparent directement, produisant ainsi des correspondances beaucoup plus précises.</p><p>En théorie, on pourrait utiliser un reranker directement pour la recherche d’informations, en comparant chaque requête à chaque document stocké, mais cela serait extrêmement coûteux en calcul et peu réaliste, sauf pour de très petites collections. En pratique, les rerankers sont donc surtout utilisés pour réévaluer des listes restreintes de correspondances candidates identifiées par d’autres moyens, comme une recherche par embeddings ou d’autres algorithmes de recherche. Les modèles de reclassification conviennent parfaitement aux architectures de recherche hybrides ou fédérées, où une requête peut être envoyée à plusieurs systèmes de recherche distincts, chacun interrogeant ses propres ensembles de données et retournant des résultats différents. Ils sont très efficaces pour fusionner des résultats hétérogènes en une seule réponse de haute qualité.</p><p>La recherche basée sur des embeddings peut représenter un engagement important : elle implique de réindexer toutes vos données stockées et de revoir les attentes des utilisateurs sur les résultats. L’ajout d’un reranker à une solution de recherche existante permet de bénéficier des avantages de l’IA sans avoir à réarchitecturer toute la solution.</p><h2>Modèles de reclassification Jina</h2><h3>Jina Reranker m0</h3><p><a href="https://jina.ai/models/jina-reranker-m0/"><strong>jina-reranker-m0</strong></a> est un reclassificateur multimodal de 2,4 milliards de paramètres, qui prend en charge des requêtes textuelles et des candidats textuels et/ou visuels. C’est le modèle de référence pour la recherche documentaire visuelle, idéal pour interroger des bases contenant des PDF, captures d’écran, images modifiées ou documents semi-structurés, qu’ils soient textuels, visuels ou mixtes.</p><p>Ce modèle prend une requête et une correspondance candidate, et retourne un score. Lorsque la même requête est utilisée avec différents candidats, les scores sont comparables et peuvent être utilisés pour les classer. Il prend en charge une taille d’entrée totale allant jusqu’à 10 240 jetons, incluant la requête et le texte ou l’image candidat(e). Chaque bloc d’image de 28 × 28 pixels utilisé pour couvrir l’image compte comme un jeton pour le calcul de la taille d’entrée.</p><h3>Jina Reranker v3</h3><p><a href="https://jina.ai/models/jina-reranker-v3/"><strong>jina-reranker-v3</strong></a> est un reclassificateur textuel de 600 millions de paramètres avec des performances de pointe pour sa taille. Contrairement à <code>jina-reranker-m0</code>, il traite une requête unique et une liste pouvant aller jusqu’à 64 candidats, puis renvoie leur ordre de classement. Il prend en charge un contexte d’entrée de 131  000 jetons, incluant la requête et tous les candidats.</p><h3>Jina Reranker v2</h3><p><a href="https://jina.ai/models/jina-reranker-v2"><strong>jina-reranker-v2-base-multilingual</strong></a> est un modèle compact et polyvalent, intégrant des fonctionnalités supplémentaires comme le support de l’appel de fonction et des requêtes SQL. Pesant moins de 300 millions de paramètres, ce modèle offre un reclassement multilingue rapide, efficace et précis, avec un support supplémentaire pour la sélection de tables SQL et de fonctions externes associées aux requêtes textuelles, ce qui le rend adapté aux cas d’usage orientés agent.</p><h2>Petits modèles de langage génératif</h2><p>Les modèles de langage génératif sont des modèles comme ChatGPT d’OpenAI, Google Gemini et Claude d’Anthropic, qui acceptent des entrées textuelles ou multimédias et renvoient des sorties textuelles. Il n’existe pas de frontière claire entre les <em>grands</em> modèles de langage (LLM) et les <em>petits</em> modèles de langage (SLM), mais les difficultés pratiques liées au développement, à l’exploitation et à l’usage de LLM de pointe sont bien connues. Les modèles les plus connus ne sont pas distribués publiquement, donc nous ne pouvons qu’estimer leur taille : ChatGPT, Gemini et Claude seraient dans la fourchette des 1 à 3 milliards de milliards de paramètres (1–3×10¹²).</p><p>Exécuter ces modèles, même lorsqu’ils sont en accès libre, dépasse largement les capacités du matériel conventionnel et requiert les puces les plus avancées, organisées en réseaux massivement parallèles. Il est possible d’utiliser des LLM via des API payantes, mais cela implique des coûts importants, une forte latence, et pose des défis en matière de protection des données, de souveraineté numérique et de rapatriement hors du cloud. En outre, les coûts liés à l’entraînement et à la personnalisation de modèles de cette taille peuvent être conséquents.</p><p>C’est pourquoi de nombreuses recherches portent sur le développement de modèles plus petits qui, bien qu’ils n’offrent pas toutes les capacités des plus grands LLM, peuvent exécuter certaines tâches spécifiques avec une qualité équivalente et à moindre coût. Les entreprises déploient généralement des logiciels pour répondre à des besoins spécifiques, et les logiciels d’IA n’échappent pas à cette règle : les solutions basées sur des SLM sont souvent préférées aux LLM. Ils peuvent généralement être exécutés sur du matériel standard, sont plus rapides, consomment moins d’énergie et sont beaucoup plus faciles à personnaliser.</p><p>L’offre SLM de Jina se développe à mesure que nous cherchons à intégrer l’IA dans des solutions de recherche concrètes.</p><h2>Jina SLMs</h2><h3>ReaderLM v2</h3><p><a href="https://jina.ai/models/ReaderLM-v2"><strong>ReaderLM-v2</strong></a> est un modèle de langage génératif capable de convertir du HTML en Markdown ou en JSON, selon des schémas JSON fournis par l’utilisateur et des instructions en langage naturel.</p><p>Le prétraitement et la normalisation des données sont des étapes clés dans le développement de solutions de recherche performantes pour les données numériques, mais les données issues du web sont souvent désordonnées, et les stratégies simples de conversion montrent vite leurs limites. Au contraire, <code>ReaderLM-v2</code> propose une solution d’IA intelligente, capable de comprendre le chaos d’un arbre DOM brut issu d’une page web, et d’identifier avec robustesse les éléments utiles.</p><p>Avec 1,5 milliard de paramètres (1,5 × 10⁹), ils sont mille fois plus compacts que les LLM de dernière génération tout en offrant des performances comparables sur des tâches ciblées.</p><h3>Jina VLM</h3><p><a href="https://jina.ai/models/jina-vlm"><strong>jina-vlm</strong></a> est un modèle de langage génératif de 2,4 milliards de paramètres (2,4×10⁹), conçu pour répondre à des questions en langage naturel à propos d’images. Il offre un excellent support pour l’analyse de documents visuels, c’est-à-dire la réponse à des questions sur des captures d’écran, des présentations, des schémas ou d’autres images non naturelles.</p><p>Par exemple :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/adfa1420d079ca4fd5582eef4349b1265b378e76-950x500.png" style="width: 100%;" /><p>Ils sont également très performants pour la lecture de texte dans des images:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ea3956e7ad86f8e171841cab2c28c8b3498da1d4-1002x500.png" style="width: 100%;" /><p>Mais là où <code>jina-vlm</code> excelle vraiment, c&#x27;est dans la compréhension du contenu des images informatives et artificielles :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f68606f9d2d99e2cd616d4ff81db3574dc4e26a5-1020x700.png" style="width: 100%;" /><p>Ou :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/297e85e7e78f296388a02301e1e08fed70827423-1000x500.png" style="width: 100%;" /><p><code>jina-vlm</code> Ils conviennent parfaitement pour la génération automatique de légendes, les descriptions de produits, les balises alt pour les images, et les applications d’accessibilité pour les personnes malvoyantes. Ils ouvrent aussi la voie à des systèmes RAG (retrieval-augmented generation) capables d’utiliser des données visuelles et de permettre à des agents d’IA de traiter des images sans intervention humaine.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Premiers pas avec Elastic Agent Builder et le SDK Strands Agents]]></title>
            <description><![CDATA[Découvrez comment créer un agent avec Elastic Agent Builder, puis explorez comment utiliser cet agent via le protocole A2A orchestré à l’aide du SDK Strands Agents.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/agent-builder-a2a-strands-agents-guide</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/agent-builder-a2a-strands-agents-guide</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jonathan Simon]]></dc:creator>
            <pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/31b6a5c1c30dacbb4d5e58d1c566071e7143a0c8-1600x879.gif?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Vous avez une idée d’agent IA ? Cela implique sans doute d’exploiter des données : pour que l’agent exécute une action utile, il doit prendre une décision, et pour cela, il lui faut les bonnes données.</p><p>Elastic Agent Builder simplifie la création d’agents IA connectés aux données. Nous vous expliquons comment faire dans cet article de blog. Voici toutes les étapes pour créer un agent avec un outil MCP capable d’accéder aux données stockées dans Elastic. Nous utiliserons ensuite le SDK Strands Agents et ses fonctionnalités Agent2Agent (A2A) pour piloter l’agent. Le <a href="https://strandsagents.com/">SDK Strands Agents</a> est une plateforme de développement d’IA multi-agents, conçue pour créer des applications autonomes avec juste ce qu’il faut de code pour obtenir les résultats souhaités.</p><p>Construisons un agent IA capable de jouer à RPS+, une version revisitée du jeu classique « Pierre, feuille, ciseaux », enrichie de quelques choix supplémentaires pour les joueurs.</p><h2>Produits requis</h2><p>Voici ce dont vous avez besoin pour suivre les étapes décrites dans cet article de blog :</p><ul><li>Un éditeur de texte fonctionnant sur votre ordinateur local<ul><li>Nous utiliserons <a href="https://code.visualstudio.com/download">Visual Studio Code</a> pour suivre les exemples présentés dans cet article de blog.</li></ul></li><li><a href="https://www.python.org/downloads/">Python 3.10 ou version supérieure</a> installé localement sur votre machine</li></ul><h2>Créez un projet sans serveur</h2><p>La première étape consiste à créer un projet Elasticsearch Serverless, qui inclut Elastic Agent Builder.</p><p>Accédez à <a href="http://cloud.elastic.co/">cloud.elastic.co</a> et créez un nouveau projet Elasticsearch Serverless.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/31b6a5c1c30dacbb4d5e58d1c566071e7143a0c8-1600x879.gif" style="width: 100%;" /><h2>Créer un index et ajouter des données</h2><p>Ensuite, nous allons ajouter des données à notre projet Elasticsearch. Ouvrez les Developer Tools pour exécuter des commandes : nous allons créer un nouvel index et y insérer des données. Dans le menu principal, sélectionnez Developer Tools.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f97d5af077504463155655a9e27c171a7f974f71-1600x879.jpg" style="width: 100%;" /><p>Copiez-collez la commande PUT suivante dans la zone de saisie de requêtes de la console Developer Tools. Cette commande crée un index Elasticsearch nommé « game-docs ».</p><pre><code>PUT /game-docs
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "content": { 
        "type": "text"
      },
      "filename": { "type": "keyword" },
      "last_modified": { "type": "date" }
    }
  }
}</code></pre><p>Cliquez sur le bouton <strong>Envoyer la requête</strong> à droite de l’instruction dans Developer Tools. Une notification doit confirmer que l’index <em>game-docs</em> a bien été créé, dans la zone de réponse de Developer Tools.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/be0555a1930e4d4f58b7ed8b669c9b702532ed17-1600x880.jpg" style="width: 100%;" /><p>L’index nommé <em>game-docs</em> est l’endroit idéal pour stocker les données du jeu que nous créons. Ajoutons maintenant un document nommé <em>rps++-md</em> dans cet index, contenant toutes les données nécessaires au jeu. Copiez-collez la commande PUT suivante dans la console Developer Tools.</p><pre><code>PUT /game-docs/_doc/rps+-md
{
  "title": "Rock Paper Scissors +",
  "content": "
# Game Name
RPS+

# Starting Prompt
Let's play RPS+ !
---
What do you choose?

# Game Objects
1. Rock 🪨 👊
2. Paper 📜 🖐
3. Scissors ✄ ✌️
4. Light ☼ 👍
5. Dark Energy ☄ 🫱

# Judgement of Victory
* Rock beats Scissors
  * because rocks break scissors
* Paper beats Rock
  * because paper covers rock
* Scissors beat Paper
  * because scissors cut paper
* Rock beats Light
  * because you can build a rock structure to block out light
* Paper beats Light
  * because knowledge stored in files and paper books helps us understand light
* Light beats Dark Energy
  * because light enables humans to lighten up and laugh in the face of dark energy as it causes the eventual heat death of the universe
* Light beats Scissors
  * because light is needed to use scissors safely
* Dark Energy beats Rock
  * because dark energy rocks more than rocks. It rocks rocks and everything else in its expansion of the universe
* Dark Energy beats Paper
  * because humans, with their knowledge stored in files and paper books, can't explain dark energy 
* Scissors beat Dark Energy
  * because a human running with scissors is darker than dark energy

# Invalid Input
I was hoping for an worthy opponent
  - but alas it appears that time has past
  - but alas there's little time for your todo list when [todo:fix this] is so vast

# Cancel Game
The future belongs to the bold. Goodbye..
",
  "filename": "RPS+.md",
  "last_modified": "2025-11-25T12:00:00Z"
}</code></pre><p>Cliquez sur le bouton <strong>Envoyer la requête</strong> à côté de l’instruction pour l’exécuter et ajouter le document <em>rps++-md</em> à l’index game-docs.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3c01d8a4602de5c33337457591a388a4a4e3fad3-1600x879.jpg" style="width: 100%;" /><p>Nous avons maintenant des données à interroger, et avec Agent Builder, c’est plus simple que jamais.</p><p>Dans le menu de navigation principal, sélectionnez <strong>Agents</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/82dbd2e9a439cabd5a5eea3d0ce005b87df0c3ea-1600x879.jpg" style="width: 100%;" /><p>Ensuite, il vous suffit de demander à l&#x27;agent Elastic AI par défaut, « Quelles données ai-je ? »</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f7a2f39c9d1486bdf02d9e88a732b540ac2e2cd1-1600x872.gif" style="width: 100%;" /><p>L’agent IA Elastic analyse les données et fournit une explication claire des informations disponibles.</p><h2>Créer un outil</h2><p>Nous avons maintenant des données dans Elastic, mettons-les à profit. Agent Builder inclut un support natif pour créer des outils <a href="https://modelcontextprotocol.io/">MCP</a>, qui permettent aux agents d’accéder aux données dont ils ont besoin pour disposer du bon contexte. Créons un outil simple pour récupérer les données de notre jeu.</p><p>Cliquez sur le menu des actions dans Agent Builder.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0e327c202674dda33bcc0e494d2b588fa8b32e4f-1600x879.png" style="width: 100%;" /><p>Dans les options du menu, sélectionnez <strong>Afficher tous les outils</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ebf58650e9fb56750d3f0b1700fab50b44f9bdf-1600x879.png" style="width: 100%;" /><p>Cliquez sur <strong>+ Nouvel outil</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6c03a7f28b99ac2d805f34f39948979893316a00-1600x879.png" style="width: 100%;" /><p>Dans le formulaire <strong>Créer un outil</strong> , sélectionnez <strong><a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL</a> </strong> <strong>Saisissez</strong> l&#x27;outil et entrez les valeurs suivantes.</p><p>Pour <strong>Tool ID </strong>:</p><pre><code>example.get_game_docs</code></pre><p>Pour <strong>Description</strong> :</p><pre><code>Get RPS+ doc from Elasticsearch game-docs index.</code></pre><p>Pour <strong>Configuration, </strong>saisissez la requête suivante dans la zone de texte <strong>ES|QL Query</strong> :</p><pre><code>FROM game-docs | WHERE filename == "RPS+.md"</code></pre><p>Le formulaire <strong>Créer outil</strong> complété devrait ressembler à ceci. Cliquez sur <strong>Enregistrer</strong> pour créer l&#x27;outil.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b6c93e344600f319b9d2c3030020cf2d171ac1c4-1600x1312.png" style="width: 100%;" /><p>Un nouvel outil a été ajouté à notre tableau de bord. Les outils ne sont pas faits pour rester inutilisés : ils doivent être mis à profit. Créons un agent qui saura exploiter notre nouvel outil personnalisé.</p><h2>Créez un agent et attribuez-lui un outil</h2><p>Créer un agent est d’une simplicité rafraîchissante avec Agent Builder. Il vous suffit de saisir quelques instructions pour l’agent, avec quelques détails : c’est tout. Créons un agent dès maintenant.</p><p>Cliquez sur <strong>Gérer les agents.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/53934b93db07187e251d4b321cb9ca647e2fd51b-1600x858.png" style="width: 100%;" /><p>Cliquez<strong> + Nouvel agent.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fae3ad8f31e71a6dfd044e1daa025a4e280b4e68-1600x490.png" style="width: 100%;" /><p>Saisissez les informations suivantes dans le formulaire <strong>Nouvel agent</strong>.</p><p>Pour <strong>l&#x27;identification de l&#x27;agent, </strong>saisissez le texte ci-dessous :</p><pre><code>rps_plus_agent</code></pre><p>Dans la section texte <strong>Instructions personnalisées </strong>, saisissez les instructions suivantes :</p><pre><code>When prompted, if the prompt contains an integer, then select the corresponding numbered item in the list of "Game Objects" from your documents. Otherwise select a random game object. This is your chosen game object for a single round of the game.

# General Game Rules
* 2 players
    - the user: the person playing the game
    - you: the agent playing the game and serving as the game master
* Each player chooses a game object which will be compared and cause them to tie, win or lose.

# Start the game
1. This is the way each new game always starts. You make the first line of your response only the name of your chosen game object. 

2. The remainder of your response should be the "Starting Prompt" text from your documents and generate a list of "Game Objects" for the person playing the game to choose a game object from.  

# End of Game: The game ends in one of the following three outcomes:
1. Invalid Input: If the player responds with an invalid game object choice, respond with variations of the "Invalid Input" text from your documents and then end the game.

2. Tie: The game ends in a tie if the user chooses the same game object as your game object choice.

3. Win or Lose: The game winner is decided based on the "Judgement of Victory" conditions from your documents. Compare the user's game object choice and your game object choice and determine who chose the winning game object.

# Game conclusion
Respond with a declaration of the winner of the game by outputting the corresponding text in the "Judgement of Victory" section of your documents.</code></pre><p>Pour le <strong>Nom d&#x27;affichage, </strong>entrez le texte ci-dessous :</p><pre><code>RPS+ Agent</code></pre><p>Pour la <strong>Description de l’affichage, </strong>saisissez le texte ci-dessous :</p><pre><code>An agent that plays the game RPS+</code></pre><p>Donnez à l&#x27;agent l&#x27;outil personnalisé que nous avons créé précédemment en cliquant sur l&#x27;onglet <strong>Outils</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1778f64bc3a1b4004998dc3668ef7f666788e193-1600x1390.png" style="width: 100%;" /><p>Sélectionnez uniquement <em>example.get_game_docs</em>, l&#x27;outil que nous avons créé précédemment.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7d734cd80161bcc058817482eb330ffcf1cb567b-1600x1363.png" style="width: 100%;" /><p>Cliquez sur <strong>Enregistrer</strong> pour créer le nouvel agent.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c0014faf605ce50c03679ed0d073bd9f3ae7234d-1600x468.png" style="width: 100%;" /><p>Testons notre nouvel agent. Un lien pratique vous permet de démarrer une conversation avec n’importe quel agent de la liste.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b7d6943ad90a4f68691207caf66b81742e712145-1600x560.png" style="width: 100%;" /><p>Saisissez simplement « start game » pour lancer la partie. Ça fonctionne !</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/984d008e4cc3f08cc1f101720673b0f7347c066c-1600x874.gif" style="width: 100%;" /><p>L’agent affiche son choix pour la partie en haut de sa réponse. Cela permet de visualiser le choix de l’agent et de vérifier que le jeu fonctionne comme prévu. Cela dit, connaître le choix de votre adversaire avant de jouer n’est pas idéal pour une partie de pierre-feuille-ciseaux. Pour peaufiner le jeu, on peut utiliser une plateforme d’orchestration d’agents capable de les piloter via du code.</p><p>Le SDK Strands Agents entre en scène.</p><h2>Strands Agents SDK</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c195bba6ff2754f5d8fda174a0c1d247bc283710-456x156.png" style="width: 100%;" /><p>Si vous souhaitez essayer de nouveaux frameworks de développement d&#x27;agents, le <a href="https://strandsagents.com/latest/">SDK Strands Agents</a> vaut la peine d&#x27;être essayé. <a href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">Le SDK Strands Agents a été publié par AWS (mai 2025)</a> en tant qu&#x27;implémentation <a href="https://github.com/strands-agents/sdk-python">Python</a> open source, et il existe désormais une version <a href="https://dev.to/aws/strands-agents-now-speaks-typescript-a-side-by-side-guide-12b3">Typescript</a>.</p><h2>Premiers pas avec le SDK Strands Agents en Python</h2><p>Lancez vos environnements de développement : nous allons cloner et exécuter une application d’exemple qui utilise Strands Agents pour piloter l’<em>agent RPS+</em> via le protocole A2A. Créons une version personnalisée du jeu RPS+ dans laquelle le choix de l’agent est révélé après votre propre décision, afin de préserver l’effet de surprise et le côté ludique du jeu Pierre-Feuille-Ciseaux.</p><p>Sur votre ordinateur local, ouvrez <a href="https://code.visualstudio.com/download">Visual Studio Code</a> et ouvrez un nouveau terminal.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2339cc37c89a3524f2b2a21684bc61dae958e1cf-915x460.jpg" style="width: 100%;" /><p>Dans le terminal que vous venez d’ouvrir, exécutez la commande suivante pour cloner le dépôt Elasticsearch Labs :</p><pre><code>git clone https://github.com/elastic/elasticsearch-labs</code></pre><p>Exécution de la commande <em>cd </em>suivante pour changer de répertoire dans le répertoire Elasticsearch Labs :</p><pre><code>cd elasticsearch-labs</code></pre><p>Ensuite, exécutez cette commande pour ouvrir le dépôt dans Visual Studio Code :</p><pre><code>code .</code></pre><p>Dans l&#x27;explorateur de fichiers de Visual Studio, développez les dossiers <em>supporting-blog-content</em> et <em>agent-builder-a2a-strands-agents</em> , puis ouvrez le fichier <em>elastic_agent_builder_a2a_rps+.py.</em> Voici à quoi ressemble le fichier ouvert dans Visual Studio Code :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d153b19e0e016c701576edb99ccab5af7c554f34-1484x1530.jpg" style="width: 100%;" /><p>Voici le contenu de <em>elastic_agent_builder_a2a_rps+.py </em>que vous devriez voir dans votre éditeur de texte :</p><pre><code>import asyncio
from dotenv import load_dotenv
from uuid import uuid4
import httpx
import os
import random
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
from a2a.types import Message, Part, Role, TextPart

DEFAULT_TIMEOUT = 60  # set request timeout to 1 minute


def create_message(*, role: Role = Role.user, text: str, context_id=None) -> Message:
    return Message(
        kind="message",
        role="user",
        parts=[Part(TextPart(kind="text", text=text))],
        message_id=uuid4().hex,
        context_id=context_id,
    )


async def main():
    load_dotenv()
    a2a_agent_host = os.getenv("ES_AGENT_URL")
    a2a_agent_key = os.getenv("ES_API_KEY")
    custom_headers = {"Authorization": f"ApiKey {a2a_agent_key}"}

    async with httpx.AsyncClient(
        timeout=DEFAULT_TIMEOUT, headers=custom_headers
    ) as httpx_client:
        # Get agent card
        resolver = A2ACardResolver(httpx_client=httpx_client, base_url=a2a_agent_host)
        agent_card = await resolver.get_agent_card(
            relative_card_path="/rps_plus_agent.json"
        )
        # Create client using factory
        config = ClientConfig(
            httpx_client=httpx_client,
            streaming=True,
        )
        factory = ClientFactory(config)
        client = factory.create(agent_card)
        # Use the client to communicate with the agent
        print("\nSending 'start game' message to Elastic A2A agent...")
        random_game_object = random.randint(1, 5)
        msg = create_message(text=f"start with game object {random_game_object}")
        async for event in client.send_message(msg):
            if isinstance(event, Message):
                context_id = event.context_id
                response_complete = event.parts[0].root.text
                # Get agent choice from the first line of the response
                parsed_response = response_complete.split("\n", 1)
                agent_choice = parsed_response[0]
                print(parsed_response[1])
        # User choice sent for game results from the agent
        prompt = input("Your Choice  : ")
        msg = create_message(text=prompt, context_id=context_id)
        async for event in client.send_message(msg):
            if isinstance(event, Message):
                print(f"Agent Choice : {agent_choice}")
                print(event.parts[0].root.text)


if __name__ == "__main__":
    asyncio.run(main())</code></pre><p>Examinons ce qui se passe dans ce code. En commençant par la méthode <em><code>main()</code></em> , le code commence par accéder aux variables d’environnement pour l’URL de l’agent et la clé API. Ensuite, nous utilisons ces valeurs pour créer un <code><em>httpx</em> client</code> que nous pouvons utiliser pour obtenir la carte d&#x27;agent de l&#x27;agent. Le client utilise ensuite les détails de la carte d’agent pour envoyer une demande de « démarrer la partie » à l’agent. Il est intéressant de noter que nous incluons la valeur <code>random_game_object</code> dans la requête <code>&quot;start game&quot;</code>. Cette valeur est un nombre aléatoire généré par le module <em>aléatoire</em> de la bibliothèque standard de Python. La raison pour laquelle nous faisons cela est qu&#x27;il s&#x27;avère que les puissants LLM (qui rendent possibles les agents IA) ne sont pas très doués pour le hasard. Pas de problème, Python vient à la rescousse.</p><p>En poursuivant dans le code, une fois que l’agent a répondu à la requête start game, le code extrait le choix de l’agent et le stocke dans la variable <em>agent_choice</em>. Le reste de la réponse est affiché sous forme de texte à destination de l’utilisateur final. L’utilisateur est ensuite invité à saisir son propre choix d’objet de jeu, qui est envoyé à l’agent. Le code affiche ensuite le choix de l’agent, ainsi que le résultat final du jeu selon l’agent.</p><h2>Définir l’URL de votre agent et la clé API comme variables d’environnement</h2><p>Comme l’application d’exemple sera exécutée en local, nous devons fournir au SDK Strands Agents une URL A2A et une clé API pour communiquer avec notre agent. L’application d’exemple utilise un fichier nommé <em>.env</em> pour stocker ces valeurs.</p><p>Faites une copie du fichier <em>env.example</em> et nommez le nouveau fichier <em>.env</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25ead5f15a17dedb777132a082097cffb06cae4d-1600x843.jpg" style="width: 100%;" /><p>Retournez dans Elastic Agent Builder pour récupérer les deux valeurs nécessaires.</p><p>Sélectionnez <strong>Afficher tous les outils</strong> dans le menu d&#x27;action d&#x27;Agent Builder en haut à droite de la page.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9c4f4e4a3bd76e11e0a182fa007a2f6aec7777b4-1600x880.jpg" style="width: 100%;" /><p>Cliquez sur le menu déroulant <strong>MCP Server</strong> en haut de la page Outils et sélectionnez <strong>Copier l’URL MCP Server.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6cde0de678bb6f81bef8a59deffb110ad6c6ce26-1600x882.jpg" style="width: 100%;" /><p>Collez <strong>l’URL du serveur MCP</strong> dans le <em>fichier .env</em> fichier en remplacement de la valeur d&#x27;espace réservé <strong>&lt;YOUR-ELASTIC-AGENT-BUILDER-URL&gt; </strong>. Nous devons maintenant apporter une modification à l&#x27;URL, à savoir remplacer le texte final « mcp » par « a2a », car le <a href="https://a2a-protocol.org/">protocole A2A</a> est celui que le SDK Agent Strands utilisera pour communiquer avec l&#x27;agent exécuté dans Elastic Agent Builder.</p><p>L’URL modifiée devrait ressembler à ceci :</p><pre><code>https://rps-game-project-12345a.kb.us-east-1.aws.elastic.cloud/api/agent_builder/a2a</code></pre><p>L&#x27;autre valeur dont nous avons besoin d&#x27;obtenir pendant que nous sommes ici dans Elastic Cloud est une clé d&#x27;API. Cliquez sur <strong>Elasticsearch </strong>dans la navigation principale.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/651676b9be65178cdad50b5d24f26441c0bf3f97-1600x549.jpg" style="width: 100%;" /><p>Cliquez sur le <strong>bouton Copier la clé API </strong>pour copier la clé API.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/17f1e2ed5c7682630c71e75b0b09ffb1d9036210-1600x879.jpg" style="width: 100%;" /><p>Maintenant, de retour dans Visual Studio Code, collez la Clé d&#x27;API dans le fichier <em>.env</em> fichier pour remplacer le texte de l&#x27;espace réservé <strong>&lt;YOUR-ELASTIC-API-KEY&gt; </strong>. Votre <em>.env</em> Le fichier devrait ressembler à ceci :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a357947e07f29c8c03382e00c7baedf04a399297-1600x286.jpg" style="width: 100%;" /><h2>Lancer l’application d’exemple</h2><p>Ouvrez un nouveau terminal dans Visual Studio Code.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/33e1174c68ea1ed47c7fe62ab6a6da657c606f56-1413x711.jpg" style="width: 100%;" /><p>Commencez par exécuter la commande <em>cd</em> suivante dans le terminal :</p><pre><code>cd elasticsearch-labs/supporting-blog-content/agent-builder-a2a-strands-agents</code></pre><p>Exécutez la commande suivante pour créer un environnement virtuel Python.</p><pre><code>python -m venv .venv</code></pre><p>Selon le système d’exploitation de votre machine, exécutez la commande suivante pour activer l’environnement virtuel.</p><ul><li>MacOS/Linux</li></ul><pre><code>source .venv/bin/activate</code></pre><ul><li>Windows</li></ul><pre><code>.venv\Scripts\activate</code></pre><p>L’application d’exemple utilise le SDK Strands Agents, et nous arrivons à l’étape où il faut l’installer. Exécutez la commande suivante pour installer le SDK Strands Agents avec toutes ses dépendances Python.</p><pre><code>pip install -r requirements.txt</code></pre><p>Il est temps de dégager la rampe de lancement et de commencer le compte à rebours. Nous sommes prêts à lancer cette application. Reculez un peu. Lançons-la avec la commande suivante :</p><pre><code>python elastic_agent_builder_a2a_rps+.py</code></pre><p>Le défi ? Une partie de RPS+ vous attend. Bravo et bonne chance pour la suite !</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/041df81fbf1776f09e1243af0a435c4c0af6aca1-1600x948.gif" style="width: 100%;" /><h2>Créez vos applications d&#x27;IA avec un contexte pertinent</h2><p>La création d&#x27;un agent IA fait désormais partie de vos compétences. Vous avez vu aussi combien il est facile d&#x27;utiliser Elastic Agent Builder via A2A dans des frameworks de développement d&#x27;agents comme les SDK Strands Agents. <a href="https://cloud.elastic.co/registration?utm_source=agentic-ai-category&amp;utm_medium=search-labs&amp;utm_campaign=agent-builder">Essayez Elastic</a> pour créer des agents IA connectés au contexte pertinent dans vos données personnalisées.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Présentation de la prise en charge d'Elasticsearch dans Google MCP Toolbox for Databases]]></title>
            <description><![CDATA[Découvrez les caractéristiques de la prise en charge d'Elasticsearch maintenant disponible dans Google MCP Toolbox for Databases et utilisez les outils ES|QL pour intégrer en toute sécurité votre index à n'importe quel client MCP.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/google-mcp-toolbox-elasticsearch-support</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/google-mcp-toolbox-elasticsearch-support</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Enrico Zimuel, Laurent Saint-Félix]]></dc:creator>
            <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/425a48691f436ed47c9bdfaf5d561ac122b2c472-1062x668.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans cet article, nous allons expliquer comment utiliser Google MCP Toolbox avec <a href="https://github.com/elastic/elasticsearch">Elasticsearch</a> pour créer un outil simple permettant d&#x27;extraire des informations d&#x27;un index Elasticsearch.</p><p>Nous avons récemment contribué au projet open source <a href="https://github.com/googleapis/genai-toolbox">Google MCP Toolbox for Databases</a> en ajoutant la prise en charge d&#x27;Elasticsearch en tant que base de données.</p><p>Grâce à cette nouvelle fonctionnalité, vous pouvez désormais utiliser Google MCP Toolbox pour vous connecter à Elasticsearch et &quot;dialoguer&quot; directement avec vos données.</p><h2>Elasticsearch</h2><p>Nous avons besoin d&#x27;une instance Elasticsearch en cours d&#x27;exécution. Vous pouvez activer un essai gratuit sur <a href="https://www.elastic.co/cloud">Elastic Cloud</a> ou l&#x27;installer localement via le script <a href="https://github.com/elastic/start-local">start-local</a> :</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>Cela installera Elasticsearch et Kibana sur votre ordinateur et générera une clé API à utiliser pour configurer Google MCP Toolbox.</p><p>La clé API sera affichée comme sortie de la commande précédente et stockée dans un fichier .env dans le dossier elastic-start-local.</p><h2>Installer l&#x27;ensemble de données d&#x27;exemple</h2><p>Après l&#x27;installation, connectez-vous à Kibana en utilisant le nom d&#x27;utilisateur <em>elastic</em> et le mot de passe généré par le script &quot;start-local&quot; (stocké dans un fichier .env).</p><p>Vous pouvez installer l&#x27;ensemble de données <strong>eCommerce orders </strong>disponible dans Kibana. Il comprend un seul index nommé <strong>kibana_sample_data_ecommerce</strong> contenant des informations sur 4 675 commandes provenant d&#x27;un site web. Pour chaque commande, nous disposons des informations suivantes :</p><ul><li>Informations client (nom, identifiant, date de naissance, e-mail, etc.)</li><li>Date de la commande</li><li>ID de la commande</li><li>Produits (liste de tous les produits avec prix, quantité, ID, catégorie, réduction, etc.)</li><li>SKU</li><li>Prix total (hors taxes, taxes incluses)</li><li>Quantité totale</li><li>Informations géographiques (ville, pays, continent, localisation, région)</li></ul><p>Pour installer les données d&#x27;exemple, ouvrez la page <strong>Intégrations</strong> dans Kibana (recherchez &quot;Integration&quot; dans la barre de recherche supérieure), puis installez l&#x27;ensemble de données &quot;Sample Data&quot;. Pour plus de détails, consultez la documentation ici : <a href="https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana">https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana</a>.</p><p>Cet article a pour but de montrer combien il est facile de configurer Google MCP Toolbox pour se connecter à Elasticsearch et interagir avec l&#x27;index <strong>kibana_sample_data_ecommerce</strong> en utilisant le langage naturel.</p><h2>Google MCP Toolbox</h2><p>Google MCP Toolbox est un serveur MCP open source conçu pour faciliter l&#x27;interaction sécurisée et efficace des applications et des agents IA avec les bases de données. Auparavant appelé &quot;GenAI Toolbox for Databases&quot;, le projet a été renommé après l&#x27;adoption d&#x27;une compatibilité totale avec le protocole <a href="https://www.anthropic.com/news/model-context-protocol">MCP</a> (Model Context Protocol). Son objectif est de supprimer les tâches complexes traditionnellement requises lors de la connexion d&#x27;agents à des bases de données en gérant en arrière-plan les pools de connexion, l&#x27;authentification, l&#x27;observabilité et d&#x27;autres aspects opérationnels.</p><p>Essentiellement, la boîte à outils permet aux développeurs de définir des outils réutilisables de haut niveau qui encapsulent les interactions avec la base de données. Ces outils peuvent ensuite être invoqués par n&#x27;importe quel client compatible MCP, tel qu&#x27;un agent IA, sans que le client n&#x27;ait à implémenter des requêtes SQL de bas niveau ni à gérer des connexions de base de données. Cette approche réduit considérablement la quantité de code répétitif nécessaire à la création d&#x27;agents compatibles avec les bases de données, permettant ainsi d&#x27;intégrer des opérations de données avancées en seulement quelques lignes de logique applicative. Une fois qu&#x27;un outil est défini, il peut être partagé entre plusieurs agents, frameworks, ou langages (Figure 1).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/137cea290bb70ad5da21853f9a6358cef4cf7451-1248x1056.png" style="width: 100%;" /><p>L&#x27;un des principaux avantages de la boîte à outils est le modèle de sécurité intégré. Les flux d&#x27;authentification tels que OAuth2 et OIDC sont pris en charge de manière native, ce qui permet aux développeurs d&#x27;éviter de manipuler ou de stocker des informations sensibles d&#x27;identification de base de données dans les agents. La plateforme fournit également des fonctionnalités d&#x27;observabilité via OpenTelemetry, y compris les métriques et le traçage, ce qui est essentiel pour le débogage, la surveillance et les déploiements en production. De manière générale, MCP Toolbox sert d&#x27;interface unifiée, sécurisée et extensible pour interagir avec vos données depuis n&#x27;importe quel système compatible MCP.</p><h2>Comment installer MCP Toolbox</h2><p>Vous pouvez installer le serveur MCP Toolbox sur Linux en utilisant la commande suivante :</p><pre><code>export VERSION=0.21.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox</code></pre><p>Si vous souhaitez l&#x27;installer sur macOS ou Windows, vous pouvez suivre les instructions <a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/#installing-the-server">détaillées ici</a>.</p><h2>Configurer Toolbox pour Elasticsearch</h2><p>Pour configurer MCP Toolbox pour Elasticsearch, nous devons créer un fichier <strong>tools.yaml</strong> comme suit :</p><pre><code>sources:
  my-cluster:
    kind: elasticsearch
    addresses:
      - http://localhost:9200
    apikey: <insert-here-api-key>

tools:
  customer-orders:
    kind: elasticsearch-esql
    source: my-cluster
    description: Get the orders made by a customer identified by name.
    query: |
    	FROM kibana_sample_data_ecommerce | WHERE MATCH(customer_full_name, ?name, {"operator": "AND"})
    parameters:
      - name: name
        type: string
        description: The customer name.

toolsets:
  elasticsearch-tools:
    - customer-orders</code></pre><p>Vous devez remplacer la valeur <strong>&lt;insert-here-api-key&gt;</strong> par une clé API Elasticsearch valide. Si vous exécutez Elasticsearch localement avec la commande &quot;start-local&quot;, vous trouverez la clé API dans le fichier .env généré par start-local, sous la variable <strong>ES_LOCAL_API_KEY</strong>. Si vous utilisez Elastic Cloud, vous pouvez générer une clé API en suivant la procédure <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">décrite ici</a>.</p><p>Les outils précédents contiennent la requête ES|QL suivante pour Elasticsearch :</p><pre><code>FROM kibana_sample_data_ecommerce | WHERE MATCH(customer_full_name, ?name)</code></pre><p>Si vous ne connaissez pas ES|QL, il s&#x27;agit d&#x27;un langage de requête développé par Elastic, similaire à SQL, qui peut être utilisé pour effectuer des recherches sur un ou plusieurs indices. Pour en savoir plus sur ES|QL, consultez la documentation officielle <a href="https://www.elastic.co/docs/reference/query-languages/esql">ici</a>.</p><p>La requête ci-dessus recherche toutes les commandes stockées dans l&#x27;index <strong>kibana_sample_data_ecommerce</strong> qui contiennent le nom du client spécifié, en utilisant le paramètre <strong>?name</strong> (le point d&#x27;interrogation indique un paramètre).</p><p>Le nom du client est défini dans la configuration YAML précédente en utilisant le type chaîne et la description &quot;The customer name&quot; (nom du client).</p><p>Cet outil peut être utilisé pour répondre à des questions sur les commandes d&#x27;un client, par exemple : <em>Combien de commandes le client Foo a-t-il passées en octobre 2025 ?</em></p><p>Les descriptions des outils et de leurs paramètres sont essentielles pour extraire les informations pertinentes de la requête en langage naturel de l&#x27;utilisateur. Cette extraction est réalisée à l&#x27;aide de la capacité d&#x27;<strong>appel de fonctions</strong> d&#x27;un grand modèle de langage (LLM). En pratique, un LLM peut déterminer quelle fonction (outil) doit être exécutée pour obtenir les informations nécessaires, ainsi que les paramètres appropriés pour cette fonction.</p><p>Pour plus d&#x27;informations sur les appels de fonctions, nous vous invitons à lire l&#x27;article <a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic">OpenAI function calling with Elasticsearch</a> (en anglais) par Ashish Tiwari.</p><h2>Exécuter le serveur Toolbox</h2><p>Vous pouvez exécuter le serveur MCP Toolbox à l&#x27;aide du fichier &quot;tools.yaml&quot; précédent avec la commande suivante :</p><pre><code>./toolbox --tools-file tools.yaml --ui</code></pre><p>Le paramètre<strong> –ui</strong> exécute une application web à l&#x27;adresse <a href="http://127.0.0.1:5000/ui">http://127.0.0.1:5000/ui</a> (Figure 2).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3caf2339b632bafd5847af1ed8b33b518a25b8a2-1600x314.png" style="width: 100%;" /><p>Sélectionnez <strong>Tools</strong> (Outils) &gt; <strong>customer-orders</strong> et insérez un nom de client dans le paramètre <strong>name</strong> (par ex. Gwen Sanders), puis cliquez sur le bouton <strong>Run Tool</strong> (Exécuter l&#x27;outil). Une réponse JSON devrait s&#x27;afficher comme illustré dans la figure 3.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b167e0142afb8919d9cedf6d0fa431d33d0e55f8-1600x933.png" style="width: 100%;" /><p>La configuration est terminée et MCP Toolbox peut exécuter l&#x27;outil <strong>customer-orders</strong> pour communiquer avec Elasticsearch, en lançant la requête ES|QL.</p><h2>Utiliser MCP Toolbox avec Gemini CLI</h2><p>Nous pouvons utiliser n&#x27;importe quel client MCP pour communiquer avec MCP Toolbox for Databases. Par exemple, nous pouvons choisir <a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a>, un outil en ligne de commande permettant d&#x27;utiliser Gemini. Si vous souhaitez installer Gemini CLI, suivez les instructions <a href="https://geminicli.com/docs/get-started/installation/">indiquées ici</a>.</p><p>Gemini CLI propose une extension préconfigurée pour MCP Toolbox, disponible sur <a href="https://github.com/gemini-cli-extensions/mcp-toolbox">gemini-cli-extensions/mcp-toolbox</a>. Vous pouvez installer cette extension en exécutant la commande suivante :</p><pre><code>gemini extensions install https://github.com/gemini-cli-extensions/mcp-toolbox</code></pre><p>Après l&#x27;installation, vous devez accéder au répertoire dans lequel vous avez stocké le fichier de configuration &quot;tools.yaml&quot; pour MCP Toolbox et exécuter Gemini CLI comme suit (cette étape est nécessaire pour que Gemini CLI soit automatiquement configuré avec MCP Toolbox) :</p><pre><code>gemini</code></pre><p>Un message de sortie devrait s&#x27;afficher comme illustré dans la figure 4.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0f22df6d3da13c1dc50dcb560414fa7c630eb9a7-1434x341.png" style="width: 100%;" /><p>Vous pouvez vérifier si la boîte à outils MCP est connectée en utilisant la commande suivante :</p><pre><code>/mcp list</code></pre><p>Vous devriez voir s&#x27;afficher <strong>mcp_toolbox</strong> avec les outils<strong> customer-orders</strong> répertoriés (Figure 5).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/97edbc40de9e44f469f6f3a09427532be167de0e-493x155.png" style="width: 100%;" /><p>Si MCP Toolbox est connecté à Gemini CLI, nous pouvons maintenant poser quelques questions, telles que : &quot;<em>Indiquez-moi les commandes pour la cliente Gwen Sanders</em>&quot;. Gemini CLI demande alors l&#x27;autorisation d&#x27;exécuter l&#x27;outil &quot;customer-orders&quot; depuis le serveur mcp_toolbox (voir Figure 6).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c052f3b5e49436903b804280c0065f67ee02444b-1432x284.png" style="width: 100%;" /><p>Après confirmation, Gemini CLI exécute la requête dans MCP Toolbox, reçoit une réponse JSON et l&#x27;utilise pour mettre en forme la réponse (Figure 7).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7ea5128f1705951c2757af6da4b456d394d4a080-1432x734.png" style="width: 100%;" /><p>La réponse de Gemini CLI indique que Gwen Sanders a passé une seule commande de 2 produits, pour un prix total de 132 EUR.</p><h2>SDK MCP Toolbox</h2><p>Google MCP Toolbox propose également un SDK pour accéder à toutes les fonctionnalités depuis un programme écrit en Go, Python et Javascript.</p><p>Par exemple, le SDK Python est disponible sur Github à la page suivante : <a href="https://github.com/googleapis/mcp-toolbox-sdk-python">https://github.com/googleapis/mcp-toolbox-sdk-python</a>.</p><p>Nous devons créer un agent simple pour nous connecter à MCP Toolbox. Nous devons installer les packages suivants :</p><pre><code>pip install toolbox-core
pip install google-adk</code></pre><p>Et créer un nouveau projet d&#x27;agent à l&#x27;aide la commande suivante :</p><pre><code>adk create my_agent</code></pre><p>Cela créera un nouveau répertoire nommé <strong>my_agent</strong> contenant un fichier <strong>agent.py</strong>.</p><p>Mettez à jour le fichier <strong>my_agent/agent.py</strong> avec le contenu suivant pour vous connecter à Toolbox :</p><pre><code>from google.adk import Agent
from google.adk.apps import App
from toolbox_core import ToolboxSyncClient

client = ToolboxSyncClient("http://127.0.0.1:5000")

root_agent = Agent(
    name='root_agent',
    model='gemini-2.5-flash',
    instruction="You are a helpful AI assistant designed to search information about a dataset of ecommerce orders.",
    tools=client.load_toolset(),
)

app = App(root_agent=root_agent, name="my_agent")</code></pre><p>Créez un fichier <strong>.env</strong> avec votre clé API Google :</p><pre><code>echo 'GOOGLE_API_KEY="YOUR_API_KEY"' > my_agent/.env</code></pre><p>Enfin, nous pouvons lancer l&#x27;exécution de l&#x27;agent et observer les résultats. Pour ce faire, exécutez la commande suivante :</p><pre><code>adk run my_agent</code></pre><p>Ou, vous pouvez le servir via une interface web :</p><pre><code>adk web --port 8000</code></pre><p>Dans les deux cas, vous pouvez interagir avec MCP Toolbox à l&#x27;aide d&#x27;une interface de questions-réponses. Par exemple, vous pouvez poser la question précédente : <em>Indiquez-moi les commandes de la cliente Gwen Sanders</em>.</p><p>Pour plus d&#x27;informations sur les différents SDK, consultez cette <a href="https://googleapis.github.io/genai-toolbox/sdks/">page de documentation</a>.</p><h2>Conclusion</h2><p>Dans cet article, nous avons démontré l&#x27;intégration d&#x27;Elasticsearch pour Google MCP Toolbox for Databases. À l&#x27;aide d&#x27;un simple fichier de configuration YAML, nous pouvons définir un ensemble d&#x27;outils qui traduisent les questions en langage naturel en requêtes Elasticsearch en utilisant le langage ES|QL.</p><p>Nous avons montré comment interagir avec l&#x27;ensemble de données &quot;kibana_sample_data_ecommerce&quot;, qui contient des commandes provenant d&#x27;un site web. Avec ce fichier de configuration, nous pouvons simplement lancer l&#x27;exécution du serveur MCP Toolbox et nous y connecter depuis n&#x27;importe quel client MCP.</p><p>Enfin, nous avons démontré comment utiliser Gemini CLI en tant que client pour nous connecter à MCP Toolbox for Databases et interroger les données e-commerce stockées dans Elasticsearch. Nous avons exécuté une requête en langage naturel pour récupérer des informations sur les commandes d&#x27;un client spécifique identifié par son nom.</p><p>À mesure que l&#x27;écosystème MCP se développe, ce modèle – des définitions d&#x27;outils légères soutenues par une infrastructure sécurisée et prête pour la production – crée de nouvelles possibilités pour construire des agents de plus en plus performants et sensibles aux données avec un minimum d&#x27;efforts. Qu&#x27;il s&#x27;agisse d&#x27;expérimenter localement les ensembles de données Elastic ou d&#x27;intégrer des fonctionnalités de recherche dans une application plus vaste, MCP Toolbox fournit une base fiable et extensible pour interagir avec vos données Elasticsearch en langage naturel.</p><p>Pour en savoir plus sur le développement d&#x27;applications d&#x27;IA agentique, consultez l&#x27;article <a href="https://search-labs-redesign.vercel.app/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">Construire des workflows d&#x27;IA agentique avec Elasticsearch</a> par Anish Mathur et Dana Juratoni.</p><p>Pour plus d&#x27;informations sur Google MCP Toolbox, consultez la page <a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/">vhttps://googleapis.github.io/genai-toolbox/getting-started/introduction/</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Évaluer la pertinence des requêtes de recherche à l’aide de listes de jugement]]></title>
            <description><![CDATA[Découvrez comment créer des listes de jugement pour évaluer objectivement la pertinence des requêtes de recherche et améliorer des indicateurs de performance comme le rappel, dans le cadre de tests de recherche scalable avec Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/judgment-lists-search-query-relevance-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/judgment-lists-search-query-relevance-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jhon Guzmán]]></dc:creator>
            <pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25478d0ffb228afd5d65d82312998ec1c299c565-700x490.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Les développeurs travaillant sur des moteurs de recherche rencontrent souvent le même problème : l’équipe métier n’est pas satisfaite d’un résultat de recherche, car les documents attendus en tête des résultats apparaissent en troisième ou quatrième position.</p><p>Mais en corrigeant ce cas précis, vous risquez de détériorer d’autres requêtes, faute de pouvoir tester chaque cas manuellement. Mais comment vérifier, vous ou votre équipe QA, si une modification d’une requête a un effet en cascade sur les autres ? Et surtout, comment s’assurer que les modifications apportées ont réellement amélioré une requête ?</p><h2>Vers une évaluation systématique</h2><p>C’est là que les listes de jugement prennent tout leur sens. Plutôt que de recourir à des tests manuels et subjectifs à chaque changement, vous pouvez définir un ensemble fixe de requêtes pertinentes pour votre cas d’usage, avec leurs résultats attendus.</p><p>Cet ensemble vous sert de référence. À chaque modification, vous l’utilisez pour déterminer si votre recherche s’est effectivement améliorée ou non.</p><p>Ce qui rend cette approche si précieuse :</p><ul><li><strong>Élimine l’incertitude</strong> : plus besoin de vous demander si vos changements impactent d’autres requêtes – les données vous le diront.</li><li><strong>Met fin aux tests manuels</strong> : une fois les ensembles de jugement enregistrés, le test devient automatique.</li><li><strong>Accompagne les changements</strong> : vous pouvez mettre en évidence des métriques claires qui confirment les bénéfices d’une modification.</li></ul><h2>Comment constituer votre liste de jugement</h2><p>L’une des façons les plus simples de commencer consiste à choisir une requête représentative et à sélectionner manuellement les documents pertinents. Deux approches sont possibles pour construire cette liste :</p><ul><li><strong>Jugements binaires :</strong> Chaque document associé à une requête reçoit une <strong>étiquette simple</strong> : <em>pertinent</em> (1) ou non pertinent (0).</li><li><strong>Jugements gradués :</strong> chaque document reçoit ici un score selon différents niveaux. Exemple : une échelle de 0 à 4, semblable à une <a href="https://en.wikipedia.org/wiki/Likert_scale">échelle de Likert</a>, où 0 signifie « pas du tout pertinent » et 4 « totalement pertinent », avec des nuances comme « pertinent », « plus ou moins pertinent », etc.</li></ul><p>Les jugements binaires sont adaptés lorsque l’intention de recherche est bien définie : ce document doit-il apparaître dans les résultats ou non ?</p><p>Les jugements gradués sont utiles lorsque la frontière est plus floue : certains résultats sont meilleurs que d’autres. On peut ainsi distinguer des résultats « très pertinents », « pertinents » ou « inutiles », et utiliser des métriques qui prennent en compte l’ordre des résultats ainsi que les retours des utilisateurs. Mais les échelles graduées présentent aussi des inconvénients : les évaluateurs peuvent interpréter différemment les niveaux de notation, ce qui nuit à la cohérence des jugements. De plus, comme les métriques graduées accordent plus de poids aux notes élevées, une légère variation (par exemple, noter 3 au lieu de 4) peut entraîner un changement bien plus important dans la métrique que ce que l’évaluateur avait anticipé. Cette part de subjectivité rend les jugements gradués plus bruyants et plus difficiles à gérer dans le temps.</p><h2>Dois-je classer les documents moi-même ?</h2><p>Pas forcément, car il existe plusieurs façons de créer votre liste de jugement, chacune avec ses avantages et ses inconvénients :</p><ul><li><strong>Jugements explicites :</strong> des experts métier examinent chaque couple requête/document et évaluent manuellement le niveau de pertinence. Cela garantit qualité et contrôle, mais c’est moins scalable.</li><li><strong>Jugements implicites :</strong> cette méthode déduit les documents pertinents à partir du comportement réel des utilisateurs : clics, taux de rebond, achats, etc. Elle permet de collecter automatiquement des données, mais celles-ci peuvent être biaisées. Par exemple, les utilisateurs ont tendance à cliquer plus souvent sur les premiers résultats, même s’ils ne sont pas pertinents.</li><li><strong>Jugements générés par l’IA :</strong> cette dernière option utilise des modèles (comme les LLM) pour évaluer automatiquement les requêtes et les documents – on parle souvent de<a href="https://en.wikipedia.org/wiki/LLM-as-a-Judge"> jurys LLM</a>. C’est rapide et facilement scalable, mais la qualité des données dépend du modèle utilisé et de la pertinence de ses données d’entraînement vis-à-vis de vos <a href="http://interests.as/">objectifs</a> métier. Comme pour les évaluations humaines, les jurys LLM peuvent introduire leurs propres biais ou incohérences. Il est donc essentiel de valider leurs résultats à l’aide d’un ensemble restreint de jugements de confiance. Les modèles LLM sont de nature probabiliste, il n’est donc pas rare de voir un modèle LLM attribuer des scores différents à un même résultat, même avec un paramètre de <a href="https://www.ibm.com/think/topics/llm-temperature">température</a> réglé sur 0.</li></ul><p>Voici quelques recommandations pour choisir la méthode la plus adaptée à la création de votre ensemble de jugement :</p><ul><li>Décidez des fonctionnalités critiques pour lesquelles seuls les utilisateurs peuvent vraiment juger (prix, marque, langue, style, détails du produit, etc.). Si ces éléments sont critiques, vous avez besoin de <strong>jugements explicites</strong> – au moins pour une partie de votre <em>liste de jugement</em>.</li><li>Utilisez des <strong>jugements implicites</strong> lorsque votre moteur de recherche génère déjà suffisamment de trafic pour que vous puissiez exploiter les clics, conversions et temps passés comme métriques de tendance. Il reste essentiel d’interpréter ces résultats avec prudence, en les comparant à des jugements explicites afin d’éviter tout biais (ex. : les utilisateurs ont tendance à cliquer sur les premiers résultats, même si des documents plus pertinents apparaissent plus bas).</li></ul><p>Pour y remédier, des techniques de réduction des biais de position permettent d’ajuster ou de repondérer les données de clics afin de mieux refléter l’intérêt réel des utilisateurs. Parmi les approches possibles :</p><ul><li><strong>Changement d’ordre des résultats</strong> : permet de modifier l’ordre des résultats de recherche pour un sous-ensemble d’utilisateurs afin d’évaluer l’effet de la position sur les clics.</li><li>Les <strong>modèles de clic</strong> incluent<strong></strong><a href="https://wiki.math.uwaterloo.ca/statwiki/index.php?title=a_Dynamic_Bayesian_Network_Click_Model_for_web_search_ranking"> : Dynamic Bayesian Network <strong>(DBN)</strong></a>, <a href="https://rsrikant.com/papers/kdd10.pdf">User Browsing Model</a> <a href="https://rsrikant.com/papers/kdd10.pdf"><strong>(UBM)</strong></a>, etc. Ces modèles statistiques estiment la probabilité qu’un clic reflète un véritable intérêt et non simplement une position dans la page, en prenant en compte des facteurs comme le défilement, la durée du clic, la séquence de navigation, et le retour aux résultats.</li></ul><h2>Exemple : application de notation de films</h2><h3>Produits requis</h3><p>Pour exécuter cet exemple, vous avez besoin d&#x27;un cluster Elasticsearch 8.x en cours d&#x27;exécution, <a href="https://www.elastic.co/downloads/elasticsearch">en local</a> ou sur <a href="https://www.elastic.co/cloud/cloud-trial-overview">Elastic Cloud Hosted</a> (hébergé ou sans serveur), ainsi que d&#x27;un accès à l&#x27;<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis">API REST</a> ou à Kibana.</p><p>Imaginez une application dans laquelle les utilisateurs peuvent publier leurs avis sur des films et aussi rechercher des films à regarder. Comme ces textes sont rédigés par les utilisateurs eux-mêmes, ils peuvent contenir des fautes de frappe ou de nombreuses variations dans la façon de s’exprimer. Il est donc essentiel que le moteur de recherche puisse interpréter cette diversité et fournir des résultats utiles aux utilisateurs.</p><p>Afin de pouvoir tester différentes requêtes sans impacter le comportement global de la recherche, l’équipe métier de votre entreprise a créé l’ensemble de jugement binaire suivant, basé sur les recherches les plus fréquentes :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Requête</td><td style="border: 1px solid #ccc; padding: 4px;">DocID</td><td style="border: 1px solid #ccc; padding: 4px;">Texte</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Performance de DiCaprio</td><td style="border: 1px solid #ccc; padding: 4px;">doc1</td><td style="border: 1px solid #ccc; padding: 4px;">La performance de DiCaprio dans The Revenant était époustouflante.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Performance de DiCaprio</td><td style="border: 1px solid #ccc; padding: 4px;">doc2</td><td style="border: 1px solid #ccc; padding: 4px;">Inception montre Leonardo DiCaprio dans l’un de ses rôles les plus emblématiques.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Performance de DiCaprio</td><td style="border: 1px solid #ccc; padding: 4px;">doc3</td><td style="border: 1px solid #ccc; padding: 4px;">Brad Pitt offre une performance solide dans ce thriller criminel.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Performance de DiCaprio</td><td style="border: 1px solid #ccc; padding: 4px;">doc4</td><td style="border: 1px solid #ccc; padding: 4px;">Une aventure riche en action avec des effets visuels impressionnants.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">films tristes qui vous font pleurer</td><td style="border: 1px solid #ccc; padding: 4px;">doc5</td><td style="border: 1px solid #ccc; padding: 4px;">Une histoire bouleversante d’amour et de perte qui m’a fait pleurer pendant des heures.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">films tristes qui vous font pleurer</td><td style="border: 1px solid #ccc; padding: 4px;">doc6</td><td style="border: 1px solid #ccc; padding: 4px;">Un des films les plus tristes jamais réalisés — apportez des mouchoirs !</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">films tristes qui vous font pleurer</td><td style="border: 1px solid #ccc; padding: 4px;">doc7</td><td style="border: 1px solid #ccc; padding: 4px;">Une comédie légère qui vous fera rire</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">films tristes qui vous font pleurer</td><td style="border: 1px solid #ccc; padding: 4px;">doc8</td><td style="border: 1px solid #ccc; padding: 4px;">Une épopée de science-fiction pleine d’action et de rebondissements.</td>
                    </tr>
              </tbody>
            </table>
          <p>Création de l&#x27;index :</p><pre><code>PUT movies
{
  "mappings": {
    "properties": {
      "text": {
        "type": "text"
      }
    }
  }
}</code></pre><p>Requête BULK :</p><pre><code>POST /movies/_bulk
{ "index": { "_id": "doc1" } }
{ "text": "DiCaprio performance in The Revenant was breathtaking." }
{ "index": { "_id": "doc2" } }
{ "text": "Inception shows Leonardo DiCaprio in one of his most iconic roles." }
{ "index": { "_id": "doc3" } }
{ "text": "Brad Pitt delivers a solid performance in this crime thriller." }
{ "index": { "_id": "doc4" } }
{ "text": "An action-packed adventure with stunning visual effects." }
{ "index": { "_id": "doc5" } }
{ "text": "A heartbreaking story of love and loss that made me cry for hours." }
{ "index": { "_id": "doc6" } }
{ "text": "One of the saddest movies ever made -- bring tissues!" }
{ "index": { "_id": "doc7" } }
{ "text": "A lighthearted comedy that will make you laugh." }
{ "index": { "_id": "doc8" } }
{ "text": "A science-fiction epic full of action and excitement." }</code></pre><p>Voici la requête Elasticsearch utilisée par l’application :</p><pre><code>GET movies/_search
{
 "query": {
   "match": {
     "text": {
       "query": "DiCaprio performance",
       "minimum_should_match": "100%"
     }
   }
 }
}</code></pre><h3>Du jugement aux métriques</h3><p>À elles seules, les listes de jugement fournissent peu d’informations : elles ne font qu’exprimer une attente vis-à-vis des résultats de nos requêtes. Elles révèlent tout leur intérêt lorsqu’elles servent à calculer des métriques objectives pour évaluer les performances de la recherche.</p><p>Aujourd’hui, la plupart des métriques les plus courantes incluent</p><ul><li><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision">Précision</a> : </strong>mesure la proportion de résultats réellement pertinents parmi tous les résultats de recherche.</li><li><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall">Rappel</a> : </strong>mesure la proportion de documents pertinents que le moteur de recherche a trouvés parmi tous les résultats.</li><li><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#_discounted_cumulative_gain_dcg">Gain Cumulé Actualisé (DCG)</a> : </strong>mesure la qualité du classement des résultats, en tenant compte du fait que les documents les plus pertinents devraient apparaître en haut de la liste.</li><li><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#_mean_reciprocal_rank"><strong>Rang réciproque moyen (MRR) :</strong></a> mesure la position du premier résultat pertinent. Plus un document est haut dans la liste, plus son score est élevé.</li></ul><p>En reprenant l’exemple de l’application de notation de films, nous allons calculer la métrique de rappel pour vérifier si des informations sont ignorées par nos requêtes.</p><p>Dans Elasticsearch, nous pouvons utiliser les <em>listes de jugement</em> pour calculer ces métriques via l’<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">API Ranking Evaluation</a>. Cette API prend en entrée la liste de jugement, la requête et la métrique à évaluer, puis retourne une valeur qui correspond à une comparaison du résultat de la requête avec la liste de jugement.</p><p>Lançons la liste de jugement pour les deux requêtes dont nous disposons :</p><pre><code>POST /movies/_rank_eval
{
 "requests": [
   {
     "id": "dicaprio-performance",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "DiCaprio performance",
             "minimum_should_match": "100%"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc1",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc2",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc3",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc4",
         "rating": 0
       }
     ]
   },
   {
     "id": "sad-movies",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "sad movies that make you cry",
             "minimum_should_match": "100%"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc5",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc6",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc7",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc8",
         "rating": 0
       }
     ]
   }
 ],
 "metric": {
   "recall": {
     "k": 10,
     "relevant_rating_threshold": 1
     }
 }
}</code></pre><p>Nous allons utiliser deux requêtes avec rank_eval : une pour la requête sur DiCaprio et une autre pour les films tristes. Chaque requête est accompagnée de sa propre liste de jugement (notations). Il n’est pas nécessaire d’évaluer tous les documents : ceux qui ne figurent pas dans la liste de notation sont simplement considérés comme non jugés. Pour effectuer les calculs, la métrique de rappel ne prend en compte que l’« ensemble pertinent », c’est-à-dire les documents jugés pertinents dans l’évaluation.</p><p>Dans ce cas, la requête sur DiCaprio obtient un rappel de 1, tandis que celle sur les films tristes obtient 0. Autrement dit, nous avons récupéré tous les résultats pertinents pour la première requête, et aucun pour la seconde. Le rappel moyen est donc de 0,5.</p><pre><code>{
 "metric_score": 0.5,
 "details": {
   "dicaprio-performance": {
     "metric_score": 1,
     "unrated_docs": [],
     "hits": [
       {
         "hit": {
           "_index": "movies",
           "_id": "doc1",
           "_score": 2.4826927
         },
         "rating": 1
       },
       {
         "hit": {
           "_index": "movies",
           "_id": "doc2",
           "_score": 2.0780432
         },
         "rating": 1
       }
     ],
     "metric_details": {
       "recall": {
         "relevant_docs_retrieved": 2,
         "relevant_docs": 2
       }
     }
   },
   "sad-movies": {
     "metric_score": 0,
     "unrated_docs": [],
     "hits": [],
     "metric_details": {
       "recall": {
         "relevant_docs_retrieved": 0,
         "relevant_docs": 2
       }
     }
   }
 },
 "failures": {}
}</code></pre><p>Peut-être que nous sommes trop stricts avec le paramètre minimum_should_match : en exigeant que 100 % des mots de la requête soient présents dans les documents, nous risquons d’écarter des résultats pertinents. Supprimons le paramètre <strong>minimum_should_match</strong>, afin qu’un document soit considéré comme pertinent dès qu’un seul mot de la requête est présent.</p><pre><code>POST /movies/_rank_eval
{
 "requests": [
   {
     "id": "dicaprio-performance",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "DiCaprio performance"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc1",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc2",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc3",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc4",
         "rating": 0
       }
     ]
   },
   {
     "id": "sad-movies",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "sad movies that make you cry"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc5",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc6",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc7",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc8",
         "rating": 0
       }
     ]
   }
 ],
 "metric": {
   "recall": {
     "k": 10,
     "relevant_rating_threshold": 1
     }
 }
}</code></pre><p>Comme vous pouvez le constater, en supprimant le paramètre <strong>minimum_should_match</strong> dans l&#x27;une des deux requêtes, nous obtenons maintenant un rappel moyen de 1 dans les deux cas.</p><pre><code>{
  "metric_score": 1,
  "details": {
    "dicaprio-performance": {
      "metric_score": 1,
      "unrated_docs": [],
      "hits": [
        {
          "hit": {
            "_index": "movies",
            "_id": "doc1",
            "_score": 2.0661702
          },
          "rating": 1
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc3",
            "_score": 0.732218
          },
          "rating": 0
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc2",
            "_score": 0.6271719
          },
          "rating": 1
        }
      ],
      "metric_details": {
        "recall": {
          "relevant_docs_retrieved": 2,
          "relevant_docs": 2
        }
      }
    },
    "sad-movies": {
      "metric_score": 1,
      "unrated_docs": [],
      "hits": [
        {
          "hit": {
            "_index": "movies",
            "_id": "doc7",
            "_score": 2.1307156
          },
          "rating": 0
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc5",
            "_score": 1.3160692
          },
          "rating": 1
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc6",
            "_score": 1.190063
          },
          "rating": 1
        }
      ],
      "metric_details": {
        "recall": {
          "relevant_docs_retrieved": 2,
          "relevant_docs": 2
        }
      }
    }
  },
  "failures": {}
}</code></pre><p>En résumé, supprimer la clause minimum_should_match : 100 % permet d’obtenir un rappel parfait pour les deux requêtes.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/24d055da4348c63827ba7046fe8cafb6f47cadd8-546x628.png" style="width: 100%;" /><p>Nous l&#x27;avons fait ! N&#x27;est-ce pas ?</p><p>Pas si vite !</p><p>En augmentant le rappel, on élargit la gamme des résultats possibles. Cependant, chaque ajustement implique un compromis. D’où l’importance de définir des cas de test complets, en utilisant plusieurs métriques pour évaluer les changements.</p><p>Les listes de jugement et les métriques vous évitent d’avancer à l’aveugle lorsque vous apportez des modifications, car vous disposez désormais de données pour les justifier. La validation n’est plus manuelle ni répétitive, et vous pouvez tester vos changements sur plusieurs cas d’usage, et non plus un seul. Les tests A/B vous permettent également de tester en conditions réelles la configuration qui convient le mieux à vos utilisateurs et à votre cas d’utilisation, bouclant ainsi la boucle entre métriques techniques et résultats concrets.</p><h2>Recommandations finales pour l’utilisation des listes de jugement</h2><p>Travailler avec des listes de jugement ne consiste pas seulement à mesurer : c’est aussi construire un cadre vous permettant d’itérer en toute confiance. Pour y parvenir, voici quelques recommandations :</p><ol><li><strong>Démarrez petit, mais démarrez</strong>. Il n’est pas nécessaire d’avoir 10 000 requêtes avec 50 listes de jugement chacune. Vous devez seulement identifier les 5 à 10 requêtes les plus critiques pour votre cas d’utilisation, et définir les documents que vous attendez en haut des résultats. Cela vous donne déjà une base de travail. En général, on commence par les principales requêtes et celles qui ne renvoient aucun résultat. Vous pouvez aussi tester en partant d’une métrique simple comme la précision, puis monter en complexité.</li><li><strong>Validez avec les utilisateurs.</strong> Complétez les résultats chiffrés par des tests A/B en production. De cette façon, vous saurez si les modifications prometteuses dans les métriques ont aussi un véritable impact.</li><li><strong>Gardez la liste vivante.</strong> Votre cas d’utilisation évoluera, tout comme vos requêtes critiques. Mettez régulièrement à jour votre liste de jugement pour refléter les nouveaux besoins.</li><li><strong>Intégrez-la à vos workflows.</strong> Intégrez les listes de jugement dans vos pipelines de développement. Assurez-vous que chaque modification de configuration, de synonymes ou d’analyse de texte soit automatiquement validée à partir de votre liste de référence.</li><li><strong>Connectez les savoir-faire techniques à la stratégie.</strong> Ne vous limitez pas à des métriques techniques comme la précision ou le rappel. Utilisez les résultats d’évaluation pour éclairer vos décisions métier.</li></ol>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Créez un workflow de recherche IA financière avec LangGraph.js et Elasticsearch]]></title>
            <description><![CDATA[Apprenez à utiliser LangGraph.js avec Elasticsearch pour créer un workflow de recherche financière alimenté par l'IA qui transforme les requêtes en langage naturel en filtres dynamiques et conditionnels pour l'analyse des investissements et du marché.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/ai-agent-workflow-finance-langgraph-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/ai-agent-workflow-finance-langgraph-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/12b6057d84c6356267cd178a3c6c1a5c61123ece-2000x1256.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La création d&#x27;applications de recherche IA implique souvent la coordination de plusieurs tâches, la récupération et l&#x27;extraction de données dans un workflow fluide. LangGraph simplifie ce processus en permettant aux développeurs d&#x27;orchestrer les agents d&#x27;IA à l&#x27;aide d&#x27;une structure basée sur des nodes. Dans cet article, nous allons construire une solution financière en utilisant <a href="https://langchain-ai.github.io/langgraphjs/">LangGraph.js</a>.</p><h2>Qu&#x27;est-ce que LangGraph ?</h2><p><a href="https://langchain-ai.github.io/langgraphjs/">LangGraph</a> est un framework pour construire des agents d’IA et les orchestrer dans un workflow afin de créer des applications assistées par l’IA. LangGraph dispose d’une architecture de nodes où nous pouvons déclarer des fonctions représentant des tâches et les assigner comme nodes du workflow. Le résultat de l&#x27;interaction de plusieurs nodes sera un graphe. LangGraph fait partie du <a href="https://js.langchain.com/docs/introduction/">LangChain</a> écosystème plus large, qui fournit des outils pour construire des systèmes d&#x27;IA modulaires et composables.</p><p>Pour mieux comprendre l’utilité de LangGraph, résolvons une situation problématique en l’utilisant.</p><h2>Aperçu de la solution</h2><p>Dans une société de capital-risque, les investisseurs ont accès à une vaste base de données avec de nombreuses options de filtrage, mais lorsqu&#x27;ils veulent combiner des critères, cela devient difficile et lent. Il se peut donc que certaines start-ups pertinentes ne soient pas trouvées pour l&#x27;investissement. Cela conduit à passer beaucoup de temps à essayer d&#x27;identifier les meilleurs candidats, voire à perdre des opportunités.</p><p>Avec LangGraph et Elasticsearch, vous pouvez effectuer des recherches filtrées en utilisant le langage naturel, ce qui évite aux utilisateurs de devoir construire manuellement des requêtes complexes avec des dizaines de filtres. Pour plus de flexibilité, le workflow choisit automatiquement, en fonction de l&#x27;entrée de l&#x27;utilisateur, entre deux types de requêtes :</p><ul><li><strong>Requêtes d’investissement</strong> : elles visent les données financières et de financement des start-up, notamment les <a href="https://www.investopedia.com/articles/personal-finance/102015/series-b-c-funding-what-it-all-means-and-how-it-works.asp">tours de table</a>, la valorisation ou le <a href="https://www.investopedia.com/terms/r/revenue.asp">CA</a>. <em>Exemple :</em> « Trouvez des startups avec un financement de série A ou série B entre 8 millions et 25 millions de dollars et un chiffre d’affaires mensuel supérieur à 500 000 $. »</li><li><strong>Requêtes axées sur le marché</strong>: elles se concentrent sur <a href="https://en.wikipedia.org/wiki/Vertical_market">les secteurs d’activité</a>, <a href="https://en.wikipedia.org/wiki/Target_market">les marchés géographiques</a> ou <a href="https://www.investopedia.com/terms/b/businessmodel.asp">les modèles économiques</a>, en aidant à identifier des opportunités dans des secteurs ou régions spécifiques. <em>Exemple :</em> « Trouvez des startups de la fintech et de la santé à San Francisco, New York ou Boston. »</li></ul><p>Pour garantir la robustesse des requêtes, nous allons faire en sorte que le LLM génère des <a href="https://www.elastic.co/docs/solutions/search/search-templates">modèles de recherche</a> au lieu de <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/querydsl">requêtes DSL</a> complètes. De cette façon, vous obtenez toujours la requête souhaitée, et le LLM n&#x27;a qu&#x27;à compléter les informations manquantes sans avoir à élaborer la requête dont vous avez besoin à chaque fois.</p><h2>Ce dont vous avez besoin pour commencer</h2><ul><li>Clé API Elasticsearch</li><li>Clé d&#x27;API OpenAPI</li><li>Node 18 ou version ultérieure</li></ul><h2>Instructions étape par étape</h2><p>Dans cette section, voyons comment l&#x27;application sera présentée. Nous utiliserons <a href="https://www.typescriptlang.org/">TypeScript</a>, un sur-ensemble de JavaScript qui ajoute des types statiques. Cela rend le code plus fiable, plus facile à maintenir et plus sûr en détectant les erreurs dès le début, tout en assurant une compatibilité totale avec JavaScript.</p><p>Le flux des nœuds se présentera comme suit :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b47d7f238c4964a6febc0de7fe5e68b186f539c3-363x555.png" style="width: 100%;" /><p>L&#x27;image ci-dessus est générée par LangGraph et représente le workflow qui définit l&#x27;ordre d&#x27;exécution et la logique conditionnelle entre les nodes :</p><ul><li><strong>decideStrategy : </strong>utilise un LLM pour rechercher la requête de l&#x27;utilisateur et choisir entre deux stratégies de recherche spécialisées, axée sur l&#x27;investissement ou axée sur le marché.</li><li><strong>PrepareInvestmentSearch : </strong>extrait les valeurs de filtre de la requête et crée un modèle prédéfini mettant l&#x27;accent sur les paramètres financiers et liés au financement.</li><li><strong>PrepareMarketSearch</strong>: extrait également les valeurs des filtres, mais crée dynamiquement des paramètres en mettant l&#x27;accent sur le marché, le secteur et le contexte géographique.</li><li><strong>ExecuteSearch : </strong>envoie la recherche construite à Elasticsearch à l&#x27;aide d&#x27;un modèle de recherche et extrait les documents de démarrage correspondants.</li><li><strong>VisualiserResults : </strong>met en forme les résultats finaux sous la forme d&#x27;un résumé clair et lisible présentant les principaux attributs de la start-up tels que le financement, le secteur d&#x27;activité et le chiffre d&#x27;affaires.</li></ul><p>Ce flux comprend un <a href="https://langchain-ai.github.io/langgraphjs/how-tos/branching/?h=conditional#how-to-create-branches-for-parallel-node-execution">branchement conditionnel</a>, fonctionnant comme une instruction « si », qui détermine s&#x27;il faut rechercher le chemin d&#x27;investissement ou de recherche de marché en fonction de l&#x27;entrée de l&#x27;utilisateur. Cette logique de décision, pilotée par le LLM, rend le workflow adaptatif et conscient du contexte, un mécanisme que nous explorerons plus en détail dans les sections suivantes.</p><h3>État de LangGraph</h3><p>Avant de voir chaque node individuellement, nous devons comprendre comment les nodes communiquent et partagent les données. Pour cela, LangGraph nous permet de définir l&#x27;état du workflow. Cela définit l&#x27;état partagé qui sera transmis entre les nodes.</p><p>L’état agit comme un conteneur partagé stockant les données intermédiaires du workflow : il enregistre d’abord la requête en langage naturel de l’utilisateur, puis la stratégie de recherche choisie, les paramètres prêts pour Elasticsearch, les résultats de recherche et, pour finir, le résultat formaté.</p><p>Cette architecture permet à chaque nœud de lire et de modifier l’état, ce qui garantit un flux d’informations constant, de l’entrée de l’utilisateur jusqu’à la visualisation finale.</p><pre><code>const VCState = Annotation.Root({
  input: Annotation<string>(), // User's natural language query
  searchStrategy: Annotation<string>(), // Search strategy chosen by LLM
  searchParams: Annotation<any>(), // Prepared search parameters
  results: Annotation<any[]>(), // Search results
  final: Annotation<string>(), // Final formatted response
});</code></pre><h3>Configurer l&#x27;application</h3><p>Tout le code de cette section se trouve dans le <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch">dépôt elasticsearch-labs</a>.</p><p>Dans le dossier où l’application sera installée, ouvrez un terminal et initialisez une application Node.js avec la commande :</p><pre><code>npm init -y</code></pre><p>Nous pouvons maintenant installer les dépendances nécessaires à ce projet :</p><pre><code>npm install @elastic/elasticsearch @langchain/langgraph @langchain/openai @langchain/core dotenv zod && npm install --save-dev @types/node tsx typescript</code></pre><ul><li><strong><code>@elastic/elasticsearch</code></strong>: Permet de gérer les requêtes Elasticsearch, comme l’ingestion et la récupération des données.</li><li><strong><code>@langchain/langgraph</code></strong>: Dépendance JS pour fournir tous les outils LangGraph.</li><li><strong><code>@langchain/openai</code></strong>: Client OpenAI LLM pour LangChain.</li><li>@langchain/core : Offre les composantes de base essentielles aux applications LangChain, notamment les modèles d’invite.</li><li><strong><code>dotenv</code></strong>: Dépendance nécessaire pour utiliser les variables d&#x27;environnement en JavaScript.</li><li><strong><code>zod</code></strong>: Dépendance au type de données.</li></ul><p><code>@types/node</code> <code>tsx</code> <code>typescript</code> nous permet d&#x27;écrire et d&#x27;exécution du code TypeScript.</p><p>Créez maintenant les fichiers suivants :</p><ul><li><code>elasticsearchSetup<a href="http://ingest.ts/">.ts</a></code>: Créera les mapping d&#x27;index, chargera les données à partir d&#x27;un fichier JSON, et ingérera les données dans Elasticsearch.</li><li><a href="http://main.ts/"><code>main.ts</code></a>: inclura l’application LangGraph.</li><li><code>.env</code>: fichier pour stocker les variables d’environnement</li></ul><p>Dans le fichier <code>.env</code>, ajoutons les variables d’environnement suivantes :</p><pre><code>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"</code></pre><p>La clé APIK de l&#x27;OpenAPI ne sera pas utilisée directement dans le code ; elle sera utilisée en interne par la bibliothèque <code>@langchain/openai</code>.</p><p>Toute la logique concernant la création de mappages, la création de modèles de recherche et l’ingestion des ensembles de données se trouve dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a>. Dans les prochaines étapes, nous nous concentrerons sur le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/main.ts"><code>main.ts</code></a>. Vous pouvez également consulter l&#x27;ensemble de données pour mieux comprendre l&#x27;aspect des données sur le site <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/dataset.json"><code>dataset.json</code></a>.</p><h3>Application LangGraph</h3><p>Dans le fichier <code>main.ts</code>, importons certaines dépendances nécessaires pour consolider l&#x27;application LangGraph. Dans ce fichier, vous devez également inclure les fonctions node et la déclaration d’état. La déclaration du graphe sera effectuée dans une méthode <code>main</code> dans les prochaines étapes. Le fichier <code>elasticsearchSetup.ts</code> contiendra les aides Elasticsearch que nous allons utiliser dans les Nodes dans les étapes suivantes.</p><pre><code>import { writeFileSync } from "node:fs";
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import {
  esClient,
  ingestDocuments,
  createSearchTemplates,
  INDEX_NAME,
  INVESTMENT_FOCUSED_TEMPLATE,
  MARKET_FOCUSED_TEMPLATE,
  createIndex,
} from "./elasticsearchSetup.js";

const llm = new ChatOpenAI({ model: "gpt-4o-mini" });</code></pre><p>Ainsi que nous l’avons vu, le client LLM sera mobilisé pour générer les paramètres du modèle de recherche Elasticsearch en fonction de la question de l’utilisateur.</p><pre><code>async function saveGraphImage(app: any): Promise<void> {
  try {
    const drawableGraph = app.getGraph();
    const image = await drawableGraph.drawMermaidPng();
    const arrayBuffer = await image.arrayBuffer();

    const filePath = "./workflow_graph.png";
    writeFileSync(filePath, new Uint8Array(arrayBuffer));
    console.log(`📊 Workflow graph saved as: ${filePath}`);
  } catch (error: any) {
    console.log("⚠️  Could not save graph image:", error.message);
  }
}</code></pre><p>La méthode ci-dessus génère l&#x27;image du graphe au format png et utilise l&#x27;<a href="https://mermaid.ink/">API Mermaid.INK</a> en arrière-plan. Ceci est utile si vous souhaitez voir comment les nodes de l&#x27;application interagissent dans le cadre d&#x27;une visualisation stylisée.</p><h3>Nodes LangGraph</h3><p>À présent, voyons chaque node en détail :</p><h3>Node decideSearchStrategy</h3><p>Le node <code>decideSearchStrategy</code> analyse les entrées de l&#x27;utilisateur et détermine s&#x27;il convient d&#x27;effectuer une rechercher axée sur les investissements ou axée sur le marché. Il utilise un LLM avec un schéma de sortie structuré (défini avec Zod) pour classer le type de requête. Avant de prendre la décision, il récupère les filtres disponibles de l&#x27;index en utilisant une agrégation, en garantissant que le modèle dispose d&#x27;un contexte à jour sur les industries, les localisations et les données de financement.</p><p>Pour extraire les valeurs possibles des filtres et les envoyer au LLM, utilisons une requête d&#x27;<a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">agrégation</a> pour les récupérer directement depuis l&#x27;index Elasticsearch. Cette logique est allouée dans une méthode appelée <code>getAvailableFilters</code>:</p><pre><code>async function getAvailableFilters() {
  try {
    const response = await esClient.search({
      index: INDEX_NAME,
      size: 0,
      aggs: {
        industries: {
          terms: { field: "industry", size: 100 },
        },
        locations: {
          terms: { field: "location", size: 100 },
        },
        funding_stages: {
          terms: { field: "funding_stage", size: 20 },
        },
        business_models: {
          terms: { field: "business_model", size: 10 },
        },
        lead_investors: {
          terms: { field: "lead_investor", size: 100 },
        },
        funding_amount_stats: {
          stats: { field: "funding_amount" },
        },
      },
    });

    return response.aggregations;
  } catch (error) {
    console.error("❌ Error getting available filters:", error);
    return {};
  }
}</code></pre><p>Avec la requête d&#x27;agrégation ci-dessus, nous avons les résultats suivants :</p><pre><code>{
  "industries": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "logistics",
        "doc_count": 5
      },
      ...
    ]
  },
  "locations": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "San Francisco, CA",
        "doc_count": 4
      },
      {
        "key": "New York, NY",
        "doc_count": 3
      },
      ...
    ]
  },
  "funding_stages": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "Series A",
        "doc_count": 8
      },
      ...
    ]
  },
  "business_models": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "B2B",
        "doc_count": 13
      },
      ...
    ]
  },
  "lead_investors": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "Battery Ventures",
        "doc_count": 1
      },
      {
        "key": "Benchmark Capital",
        "doc_count": 1
      },
      ...
    ]
  },
  "funding_amount_stats": {
    "count": 20,
    "min": 4500000,
    "max": 35000000,
    "avg": 14075000,
    "sum": 281500000
  }
}</code></pre><p>Découvrez tous les résultats <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/responses/aggregationsResponse.json">ici</a>.</p><p>Pour les deux stratégies, nous allons utiliser la recherche hybride afin de détecter à la fois la partie structurée de la question (filtres) et les parties plus subjectives (sémantique). Voici un exemple des deux requêtes utilisant des <a href="https://www.elastic.co/docs/solutions/search/search-templates">modèles de recherche</a> :</p><pre><code>await esClient.putScript({
      id: INVESTMENT_FOCUSED_TEMPLATE,
      script: {
        lang: "mustache",
        source: `{
          "size": 5,
          "retriever": {
            "rrf": {
              "retrievers": [
                {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "semantic_field",
                        "query": "{{query_text}}"
                      }
                    }
                  }
                },
                {
                  "standard": {
                    "query": {
                      "bool": {
                        "filter": [
                          {"terms": {"funding_stage": {{#join}}{{#toJson}}funding_stage{{/toJson}}{{/join}}}},
                          {"range": {"funding_amount": {"gte": {{funding_amount_gte}}{{#funding_amount_lte}},"lte": {{funding_amount_lte}}{{/funding_amount_lte}}}}},
                          {"terms": {"lead_investor": {{#join}}{{#toJson}}lead_investor{{/toJson}}{{/join}}}},
                          {"range": {"monthly_revenue": {"gte": {{monthly_revenue_gte}}{{#monthly_revenue_lte}},"lte": {{monthly_revenue_lte}}{{/monthly_revenue_lte}}}}}
                        ]
                      }
                    }
                  }
                }
              ],
              "rank_window_size": 100,
              "rank_constant": 20
            }
          }
        }`,
      },
    });</code></pre><p>Regardez les requêtes détaillées dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts#L119"><code>elasticsearchSetup.ts</code></a> . Dans le node suivant, il sera décidé laquelle des deux requêtes sera utilisée :</p><pre><code>// Node 1: Decide search strategy using LLM
async function decideSearchStrategy(state: typeof VCState.State) {
  // Zod schema for specialized search strategy decision
  const SearchDecisionSchema = z.object({
    search_type: z
      .enum(["investment_focused", "market_focused"])
      .describe("Type of specialized search strategy to use"),
    reasoning: z
      .string()
      .describe("Brief explanation of why this search strategy was chosen"),
  });

  const decisionLLM = llm.withStructuredOutput(SearchDecisionSchema);

  // Get dynamic filters from Elasticsearch
  const availableFilters = await getAvailableFilters();

  const prompt = `Query: "${state.input}"
    Available filters: ${JSON.stringify(availableFilters, null, 2)}

    Choose between two specialized search strategies:
    
    - investment_focused: For queries about funding stages, funding amounts, monthly revenue, lead investors, financial performance
    
    - market_focused: For queries about industries, locations, business models, market segments, geographic markets
    
    Analyze the query intent and choose the most appropriate strategy.
  `;

  try {
    const result = await decisionLLM.invoke(prompt);
    console.log(
      `🤔 Search strategy: ${result.search_type} - ${result.reasoning}`
    );

    return {
      searchStrategy: result.search_type,
    };
  } catch (error: any) {
    console.error("❌ Error in decideSearchStrategy:", error.message);
    return {
      searchStrategy: "investment_focused",
    };
  }
}</code></pre><h3>Nodes prepareInvestmentSearch et prepareMarketSearch</h3><p>Les deux nœuds utilisent une fonction d’assistance partagée, <code>extractFilterValues</code>, qui exploite le LLM pour identifier les filtres pertinents mentionnés dans les entrées de l’utilisateur, tels que l’industrie, la localisation, le stade de financement, le modèle économique, etc. Nous utilisons ce schéma pour construire notre <a href="https://www.elastic.co/docs/solutions/search/search-templates">modèle de recherche</a>.</p><pre><code>// Extract all possible filter values from user input
async function extractFilterValues(input: string) {
  const FilterValuesSchema = z.object({
    // Investment-focused filters
    funding_stage: z
      .array(z.string())
      .default([])
      .describe("Funding stage values mentioned in query"),
    funding_amount_gte: z
      .number()
      .default(0)
      .describe("Minimum funding amount in USD"),
    funding_amount_lte: z
      .number()
      .default(100000000)
      .describe("Maximum funding amount in USD"),
    lead_investor: z
      .array(z.string())
      .default([])
      .describe("Lead investor values mentioned in query"),
    monthly_revenue_gte: z
      .number()
      .default(0)
      .describe("Minimum monthly revenue in USD"),
    monthly_revenue_lte: z
      .number()
      .default(10000000)
      .describe("Maximum monthly revenue in USD"),
    industry: z
      .array(z.string())
      .default([])
      .describe("Industry values mentioned in query"),
    location: z
      .array(z.string())
      .default([])
      .describe("Location values mentioned in query"),
    business_model: z
      .array(z.string())
      .default([])
      .describe("Business model values mentioned in query"),
  });

  const extractorLLM = llm.withStructuredOutput(FilterValuesSchema);
  const availableFilters = await getAvailableFilters();

  const extractPrompt = `Extract ALL relevant filter values from: "${input}"
    Available options: ${JSON.stringify(availableFilters, null, 2)}
    Extract only values explicitly mentioned in the query. Leave fields empty if not mentioned.`;

  return await extractorLLM.invoke(extractPrompt);
}</code></pre><p>Selon l&#x27;intention détectée, le workflow sélectionne l&#x27;un des deux chemins :</p><p><strong>prepareInvestmentSearch :</strong> définit des paramètres de rechercher orientés sur la finance, notamment l&#x27;&#x27;étape du financement, le montant du financement, les informations relatives à l&#x27;&#x27;investisseur et au renouvellement. Vous pouvez trouver le modèle complet de requête dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a> :</p><pre><code>// Node 2A: Prepare Investment-Focused Search Parameters 
async function prepareInvestmentSearch(state: typeof VCState.State) {
  console.log(
    "💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis..."
  );

  try {
    // Extract all filter values from input
    const values = await extractFilterValues(state.input);

    let searchParams: any = {
      template_id: INVESTMENT_FOCUSED_TEMPLATE,
      query_text: state.input,
      ...values,
    };

    return { searchParams };
  } catch (error) {
    console.error("❌ Error preparing investment-focused params:", error);
    return {
      searchParams: {},
    };
  }
}</code></pre><p><strong>prepareMarketSearch :</strong> crée des paramètres orientés vers le marché, axés sur les industries, les régions géographiques et les modèles économiques. Voir l’intégralité de la requête dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a> :</p><pre><code>// Node 2B: Prepare Market-Focused Search Parameters
async function prepareMarketSearch(state: typeof VCState.State) {
  console.log(
    "🔍 Preparing MARKET-FOCUSED search parameters with market emphasis..."
  );

  try {
    // Extract all filter values from input
    const values = await extractFilterValues(state.input);

    let searchParams: any = {
      template_id: MARKET_FOCUSED_TEMPLATE,
      query_text: state.input,
      ...values,
    };

    return { searchParams };
  } catch (error) {
    console.error("❌ Error preparing market-focused params:", error);
    return {};
  }
}</code></pre><h3>Node executeSearch</h3><p>Ce node prend les paramètres de rechercher générés à partir de l&#x27;état et les envoie d&#x27;abord à Elasticsearch, en utilisant l&#x27;<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-render-search-template">API _render</a> pour visualiser la requête à des fins de débogage, puis envoie une demande pour récupérer les résultats.</p><pre><code>// Node 3: Execute Search
async function executeSearch(state: typeof VCState.State) {
  const { searchParams } = state;

  try {
    // getting formed query from template for debugging
    const renderedTemplate = await esClient.renderSearchTemplate({
      id: searchParams.template_id,
      params: searchParams,
    });

    console.log(
      "📋 Complete query:",
      JSON.stringify(renderedTemplate.template_output, null, 2)
    );

    const results = await esClient.searchTemplate({
      index: INDEX_NAME,
      id: searchParams.template_id,
      params: searchParams,
    });

    return {
      results: results.hits.hits.map((hit: any) => hit._source),
    };
  } catch (error: any) {
    console.error(`❌ ${state.searchParams.search_type} search error:`, error);
    return { results: [] };
  }
}</code></pre><h3>Node visualizeResults</h3><p>Enfin, ce node affiche les résultats d’Elasticsearch.</p><pre><code>// Node 4: Visualize results
async function visualizeResults(state: typeof VCState.State) {
  const results = state.results || [];

  let formattedResults = `🎯 Found ${results.length} startups matching your criteria:\n\n`;

  results.forEach((startup: any, index: number) => {
    formattedResults += `${index + 1}. **${startup.company_name}**\n`;
    formattedResults += `   📍 ${startup.location} | 🏢 ${startup.industry} | 💼 ${startup.business_model}\n`;
    formattedResults += `   💰 ${startup.funding_stage} - $${(
      startup.funding_amount / 1000000
    ).toFixed(1)}M\n`;
    formattedResults += `   👥 ${startup.employee_count} employees | 📈 $${(
      startup.monthly_revenue / 1000
    ).toFixed(0)}K MRR\n`;
    formattedResults += `   🏦 Lead: ${startup.lead_investor}\n`;
    formattedResults += `   📝 ${startup.description}\n\n`;
  });

  return {
    final: formattedResults,
  };
}</code></pre><p>Par programmation, l&#x27;ensemble du graphe ressemble à ceci :</p><pre><code>  const workflow = new StateGraph(VCState)
    // Register nodes - these are the processing functions
    .addNode("decideStrategy", decideSearchStrategy)
    .addNode("prepareInvestment", prepareInvestmentSearch)
    .addNode("prepareMarket", prepareMarketSearch)
    .addNode("executeSearch", executeSearch)
    .addNode("visualizeResults", visualizeResults)
    // Define execution flow with conditional branching
    .addEdge(START, "decideStrategy") // Start with strategy decision
    .addConditionalEdges(
      "decideStrategy",
      (state: typeof VCState.State) => state.searchStrategy, // Conditional function
      {
        investment_focused: "prepareInvestment", // If investment focused -> RRF template preparation
        market_focused: "prepareMarket", // If market focused -> dynamic query preparation
      }
    )
    .addEdge("prepareInvestment", "executeSearch") // Investment prep -> execute
    .addEdge("prepareMarket", "executeSearch") // Market prep -> execute
    .addEdge("executeSearch", "visualizeResults") // Execute -> visualize
    .addEdge("visualizeResults", END); // End workflow</code></pre><p>Comme vous pouvez le constater, nous avons une arête conditionnelle où l&#x27;application décide quel « chemin » ou node sera exécuté ensuite. Cette fonctionnalité est utile lorsque les workflows nécessitent une logique de branchement, comme le choix entre plusieurs outils ou l’inclusion d’une étape humaine dans la boucle.</p><p>Maintenant que vous maîtrisez les fonctionnalités clés de LangGraph, nous pouvons préparer l’application qui exécutera le code :</p><p>Rassemblons tout dans une méthode <code>main</code> , ici nous déclarons le graphe avec tous les éléments sous la variable workflow :</p><pre><code>async function main() {
  await createIndex();
  await createSearchTemplates();
  await ingestDocuments();

  // Create the workflow graph with shared state
  const workflow = new StateGraph(VCState)
    // Register nodes - these are the processing functions
    .addNode("decideStrategy", decideSearchStrategy)
    .addNode("prepareInvestment", prepareInvestmentSearch)
    .addNode("prepareMarket", prepareMarketSearch)
    .addNode("executeSearch", executeSearch)
    .addNode("visualizeResults", visualizeResults)
    // Define execution flow with conditional branching
    .addEdge(START, "decideStrategy") // Start with strategy decision
    .addConditionalEdges(
      "decideStrategy",
      (state: typeof VCState.State) => state.searchStrategy, // Conditional function
      {
        investment_focused: "prepareInvestment", // If investment focused -> RRF template preparation
        market_focused: "prepareMarket", // If market focused -> dynamic query preparation
      }
    )
    .addEdge("prepareInvestment", "executeSearch") // Investment prep -> execute
    .addEdge("prepareMarket", "executeSearch") // Market prep -> execute
    .addEdge("executeSearch", "visualizeResults") // Execute -> visualize
    .addEdge("visualizeResults", END); // End workflow


  const app = workflow.compile();

  await saveGraphImage(app);

  const query =
    "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K";

  const marketResult = await app.invoke({ input: query });
  console.log(marketResult.final);
}</code></pre><p>La variable de requête simule l&#x27;entrée utilisateur saisie dans une barre de recherche hypothétique :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e8d76909eb2bc1bb62f3ca9a8b3e4b85fcec2893-1600x164.png" style="width: 100%;" /><p>D’après la phrase en langage naturel « Trouvez des startups avec un financement de la série A ou de la série B entre 8 millions et 25 millions de dollars et un chiffre d’affaires mensuel supérieur à 500 000 $ », tous les filtres seront extraits.</p><p>Enfin, invoquez la méthode principale :</p><pre><code>main().catch(console.error);</code></pre><h3>Résultats</h3><pre><code>🔍 Checking if index exists...
🏗️ Creating index...
✅ Index created successfully!
Ingesting documents...
✅ Documents ingested successfully!
✅ Investment-focused template created successfully!
✅ Market-focused template created successfully!

📊 Workflow graph saved as: ./workflow_graph.png

🔍 Query: "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"

🤔 Search strategy: investment_focused - The query specifically seeks profitable fintech startups with defined funding amounts and high monthly revenue, which aligns closely with financial performance metrics and investment-related criteria.

💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis...

📋 Complete query: {
  "size": 5,
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": {
              "semantic": {
                "field": "semantic_field",
                "query": "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"
              }
            }
          }
        },
        {
          "standard": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "terms": {
                      "funding_stage": [
                        "Series A",
                        "Series B"
                      ]
                    }
                  },
                  {
                    "range": {
                      "funding_amount": {
                        "gte": 8000000,
                        "lte": 25000000
                      }
                    }
                  },
                  {
                    "terms": {
                      "lead_investor": []
                    }
                  },
                  {
                    "range": {
                      "monthly_revenue": {
                        "gte": 500000,
                        "lte": 0
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      ],
      "rank_window_size": 100,
      "rank_constant": 20
    }
  }
}
🎯 Found 5 startups matching your criteria:

1. **TechFlow**
   📍 San Francisco, CA | 🏢 logistics | 💼 B2B
   💰 Series A - $8.0M
   👥 45 employees | 📈 $500K MRR
   🏦 Lead: Sequoia Capital
   📝 TechFlow optimizes supply chain operations using AI-powered route optimization and real-time tracking. Founded in 2023, shows remarkable growth with $500K monthly revenue.

2. **DataViz**
   📍 New York, NY | 🏢 enterprise software | 💼 B2B
   💰 Series A - $10.0M
   👥 42 employees | 📈 $450K MRR
   🏦 Lead: Battery Ventures
   📝 DataViz creates intuitive data visualization tools for enterprise customers. No-code platform allows business users to create dashboards without technical expertise.

3. **FinanceAI**
   📍 San Francisco, CA | 🏢 fintech | 💼 B2C
   💰 Series C - $25.0M
   👥 120 employees | 📈 $1200K MRR
   🏦 Lead: Tiger Global Management
   📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.

4. **UrbanMobility**
   📍 New York, NY | 🏢 logistics | 💼 B2B2C
   💰 Series B - $15.0M
   👥 78 employees | 📈 $750K MRR
   🏦 Lead: Kleiner Perkins
   📝 UrbanMobility revolutionizes urban transportation through autonomous delivery drones and smart logistics hubs. Partners with major retailers for same-day delivery across Manhattan and Brooklyn.

5. **HealthTech Solutions**
   📍 Boston, MA | 🏢 healthcare | 💼 B2B
   💰 Series B - $18.0M
   👥 95 employees | 📈 $900K MRR
   🏦 Lead: General Catalyst
   📝 HealthTech Solutions develops medical devices and software for remote patient monitoring. Comprehensive telehealth platform reducing hospital readmissions by 30%.

✨  Done in 18.80s.</code></pre><p>Pour l&#x27;entrée envoyée, l&#x27;application choisit le chemin <strong>axé sur l&#x27;investissement</strong> et, par conséquent, nous pouvons voir la requête Elasticsearch générée par le workflow, qui extrait les valeurs et les plages de l&#x27;entrée de l&#x27;utilisateur. Nous pouvons également voir la requête envoyée à Elasticsearch avec les valeurs extraites appliquées, et enfin, les résultats formatés par le nœud <code>visualizeResults</code> avec les résultats.</p><p>Testons maintenant le node <strong>axé sur le marché</strong> en utilisant la requête « Trouver des startups fintech et de la santé à San Francisco, New York ou Boston » :</p><pre><code>...

🔍 Query: Find fintech and healthcare startups in San Francisco, New York, or Boston

🤔 Search strategy: market_focused - The query is focused on finding fintech startups in San Francisco that are disrupting traditional banking and payment systems, which pertains to specific industries (fintech) and locations (San Francisco). Thus, a market-focused strategy is more appropriate.

🔍 Preparing MARKET-FOCUSED search parameters with market emphasis...

📋 Complete query: {
  "size": 5,
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": {
              "semantic": {
                "field": "semantic_field",
                "query": "Find fintech and healthcare startups in San Francisco, New York, or Boston"
              }
            }
          }
        },
        {
          "standard": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "terms": {
                      "industry": [
                        "fintech",
                        "healthcare"
                      ]
                    }
                  },
                  {
                    "terms": {
                      "location": [
                        "San Francisco, CA",
                        "New York, NY",
                        "Boston, MA"
                      ]
                    }
                  },
                  {
                    "terms": {
                      "business_model": []
                    }
                  }
                ]
              }
            }
          }
        }
      ],
      "rank_window_size": 50,
      "rank_constant": 10
    }
  }
}
🎯 Found 5 startups matching your criteria:

1. **FinanceAI**
   📍 San Francisco, CA | 🏢 fintech | 💼 B2C
   💰 Series C - $25.0M
   👥 120 employees | 📈 $1200K MRR
   🏦 Lead: Tiger Global Management
   📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.

2. **CryptoWallet**
   📍 Miami, FL | 🏢 fintech | 💼 B2C
   💰 Series B - $16.0M
   👥 73 employees | 📈 $820K MRR
   🏦 Lead: Coinbase Ventures
   📝 CryptoWallet provides secure digital wallet solutions for cryptocurrency trading and storage. Multi-chain support with enterprise-grade security features.

...

✨  Done in 7.41s.</code></pre><h2>Enseignements</h2><p>Pendant le processus d&#x27;écriture, j&#x27;ai appris :</p><ul><li>Nous devons montrer au LLM les valeurs exactes des filtres, sinon nous attendons de l&#x27;utilisateur qu&#x27;il saisisse les valeurs exactes des éléments. Pour une faible cardinalité, cette approche convient, mais lorsque la cardinalité est élevée, nous avons besoin d&#x27;un mécanisme pour filtrer les résultats</li><li>Utiliser des modèles de recherche rend les résultats bien plus cohérents que de laisser le LLM écrire la requête Elasticsearch, et c’est aussi plus rapide</li><li>Les arêtes conditionnelles constituent un mécanisme puissant pour construire des applications avec de multiples variantes et chemins de branchement.</li><li>La sortie structurée est extrêmement utile lors de la génération d&#x27;informations avec des LLM, car elle applique des réponses prévisibles et sécurisées. Cela améliore la fiabilité et réduit les erreurs d&#x27;interprétation.</li></ul><p>La combinaison de la recherche sémantique et de la recherche structurée par le biais d&#x27;une recherche hybride produit des résultats meilleurs et plus pertinents, en équilibrant précision et compréhension du contexte.</p><h2>Conclusion</h2><p>Dans cet exemple, nous combinons LangGraph.js avec Elasticsearch pour créer un workflow dynamique capable d&#x27;interpréter les requêtes en langage naturel et de décider entre des stratégies de recherche axées sur la finance ou le marché. Cette approche réduit la complexité de la création de requêtes manuelles tout en améliorant la flexibilité et la précision pour les analystes en capital-risque.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Améliorer l'interactivité des tableaux de bord Kibana grâce aux contrôles variables]]></title>
            <description><![CDATA[Découvrez comment utiliser les contrôles variables dans Kibana 8.18+ pour filtrer les visualisations individuelles, ajuster les intervalles de temps et regrouper les données par champs dans les tableaux de bord Kibana.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/kibana-dashboard-interactivity-variable-controls-overview</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/kibana-dashboard-interactivity-variable-controls-overview</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Teresa Alvarez Soler]]></dc:creator>
            <pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/59aa3adffc8c759e42b961ef7d63719ce232893a-1348x830.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Nous sommes ravis d&#x27;annoncer que <strong>les contrôles variables sont désormais disponibles dans les tableaux de bord Kibana</strong> à partir de la version 8.18 et pour toute la série 9.x ! Cette fonctionnalité est l&#x27;un des ajouts les plus demandés par les utilisateurs des tableaux de bord, et elle est enfin là 🎉 Au cours des derniers mois, nous avons continué à développer et affiner <a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls#add-variable-control">les contrôles variables</a>. C&#x27;est donc le moment parfait pour leur consacrer un article de blog.</p><h2>Qu&#x27;est-ce que les contrôles variables ?</h2><p>Si vous avez déjà travaillé avec des tableaux de bord Kibana, vous connaissez probablement nos contrôles de tableau de bord classiques : ces menus déroulants pratiques affichent des valeurs extraites de vos données pour vous permettre de les filtrer en quelques clics.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b82c1b25a0b38661e5ce4552f763be487d5074aa-1600x701.png" style="width: 100%;" /><p>Les contrôles variables semblent similaires à première vue, mais ils comportent une particularité astucieuse : au lieu de filtrer automatiquement chaque panneau de votre tableau de bord, ils peuvent être directement intégrés dans <a href="https://www.elastic.co/docs/explore-analyze/visualize/esorql">des requêtes ES|QL au sein de visualisations individuelles</a>.</p><p>Cela signifie que <em>vous</em> décidez où chaque contrôle s&#x27;applique. Mieux encore, vous pouvez les utiliser pour toutes sortes de choses, comme ajuster les intervalles de temps, changer les champs de répartition ou modifier les paramètres de visualisation à la volée. En résumé, ils offrent une expérience véritablement interactive pour vos tableaux de bord et permettent ainsi d&#x27;obtenir des informations plus rapidement et plus facilement.</p><h2>Cas d&#x27;utilisation des contrôles variables</h2><p>Les contrôles variables semblent utiles, mais qu&#x27;offrent-ils vraiment ? Voici quelques exemples de la manière dont ils améliorent vos tableaux de bord :</p><h3>Filtrez les visualisations sélectionnées</h3><p>Vous souhaitez filtrer <em>certaines</em> visualisations seulement ? C&#x27;est possible avec les contrôles variables. Choisissez les panneaux sur lesquels vous souhaitez agir et connectez-les dans les requêtes ES|QL à l&#x27;origine de vos visualisations.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/efa367363830b03bc67028aceafe78c4b44e578f-1440x562.gif" style="width: 100%;" /><h3>Sélectionnez différents intervalles de temps</h3><p>Donnez à vos utilisateurs la possibilité de choisir entre « 5 minutes », « 1 heure », « 1 jour », ou tout autre intervalle temporel souhaité. Créez un contrôle variable avec des intervalles prédéfinis et connectez-le à votre requête de série temporelle.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/62aa9f4e728036f8c70213b76b1cf131f36f5b4d-1440x606.gif" style="width: 100%;" /><h3>Modifier les fonctions</h3><p>Au lieu de créer plusieurs graphiques pour chaque opération, les utilisateurs du tableau de bord peuvent choisir s&#x27;ils veulent voir le maximum, la moyenne, différents centiles ou tout autre agrégateur.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f6a2b4c73dc35efe462c2924a153d7b3fa3a7922-1436x606.gif" style="width: 100%;" /><h3>Grouper par différents champs</h3><p>Il est parfois nécessaire de décomposer les données selon différents facteurs lors d&#x27;une enquête. Grâce aux contrôles variables, vous pouvez définir plusieurs champs « grouper par » et permettre aux utilisateurs du tableau de bord de choisir celui qui les aidera à obtenir les informations souhaitées.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fe8745a6fddccadba0666686b8ebc67fdaf64158-1438x606.gif" style="width: 100%;" /><h2>Comment faire ?</h2><p>Le moyen le plus simple (et probablement le plus agréable) de créer un contrôle variable est directement depuis l&#x27;<strong>éditeur de requêtes ES|QL</strong> dans votre visualisation. Commencez simplement à taper votre requête, utilisez le menu de saisie automatique et Kibana vous fournira la structure de contrôle nécessaire.</p><p>Mais si vous préférez partir de la variable elle-même, vous pouvez également aller dans : <strong>Add panel → Controls → Variable control (Ajouter un panneau → Contrôles → Contrôle de variable)</strong> et ajouter la variable à vos visualisations après avoir créé le contrôle.</p><h3>Exemple 1 : Contrôle de filtrage avec sélection à valeurs multiples</h3><p>1. Choisissez une visualisation alimentée par une requête ES|QL et cliquez sur « Create control » (Créer un contrôle) dans la clause WHERE</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/46cb6f2a6775aee152d42eb5ee85170f1bdf26cb-1600x668.png" style="width: 100%;" /><p>2. Vous serez automatiquement redirigé vers le menu de création de variable, où le type « Values from a query » (Valeurs d’une requête) sera sélectionné pour vous, et le nom de la variable déjà pré-rempli. N’oubliez pas que le nom d’un contrôle doit toujours commencer par « ?... » pour fonctionner dans la requête de visualisation.</p><p>Vous aurez traditionnellement besoin d&#x27;une requête comme celle-ci pour obtenir les valeurs d&#x27;un champ et les mettre à jour selon l&#x27;intervalle de temps sélectionné dans le tableau de bord :</p><pre><code>FROM <datasource_name>
| WHERE @timestamp <=?_tend and @timestamp >?_tstart
| STATS BY <field_name></code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a2a72d4e3159923c6207908da9b4172e27cd5f81-1600x716.png" style="width: 100%;" /><p>3. Lorsque vous enregistrez le contrôle, vous le verrez apparaître en haut du tableau de bord et votre requête de visualisation sera mise à jour avec le nom du contrôle variable.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5fc434c8951889e9769652b675191711d126a685-1600x653.png" style="width: 100%;" /><p>4. Si vous souhaitez ajouter <a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls#esql-multi-values-controls">une sélection à valeurs multiples</a> au contrôle, vous devez utiliser la fonction <code>MV_CONTAINS</code> dans la requête et sélectionner « Allow multiple selections » (Autoriser les sélections multiples) lors de la création du contrôle à l’étape 2 (disponible à partir de la version 9.3).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1f237cea0a37cb25a7917a2a683707a269adae8e-1600x670.png" style="width: 100%;" /><h3>Exemple 2 : contrôle de l&#x27;intervalle de temps</h3><p>Si vous créez une série temporelle, vous pouvez facilement ajouter un contrôle variable pour l&#x27;intervalle de votre histogramme de dates :</p><p>1. Lors de la rédaction d&#x27;une requête ES|QL pour votre série temporelle, cliquez sur « Create control » (Créer un contrôle). Lorsque vous créez une variable pour des intervalles, il est préférable d&#x27;utiliser <code>TBUCKET</code> au lieu de <code>BUCKET</code> pour prendre en charge des intervalles plus lisibles tels que « 1 heure », « 1 jour », etc. Une option automatique sera bientôt disponible pour <code>TBUCKET</code> afin qu&#x27;il puisse s&#x27;adapter automatiquement aux périodes de temps.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b0ad53d790ff9bdd42db5e77477318319f423534-1600x664.png" style="width: 100%;" /><p>2. Définissez les intervalles pour remplir les options dans le menu déroulant.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f3bd83f530cfa4698c1a3b1ae60d08d0414043b5-1600x757.png" style="width: 100%;" /><p>3. Sélectionnez différents intervalles dans le menu déroulant et observez comment votre visualisation change.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0f928d9c70929f64926e065059188d140cd48943-1600x671.png" style="width: 100%;" /><h3>Exemple 3 : variables pour les fonctions</h3><ol><li>Créez une variable en utilisant le type de contrôle « Static values » (Valeurs statiques) et ajoutez des noms de fonctions à vos valeurs déroulantes. Il est important d&#x27;utiliser un nom de variable qui commence par « ??… » pour remplacer les fonctions.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/531444237b7e152d3c8a6f3ca7e464f954f9e856-1600x663.png" style="width: 100%;" /><p>2. Incluez le nom de la variable dans votre requête ES|QL.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9858442abb26d8d266d464852871b139fde63b89-1600x665.png" style="width: 100%;" /><h3>Exemple 4 : variables pour les champs</h3><ol><li>Vous pouvez utiliser le type de contrôle « Static values » (Valeurs statiques) et indiquer les noms des champs que vous souhaitez. Le nom de variable doit commencer par « ??… » pour les champs.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/33534c3df2fae024b25c28b4aed5d742e54202a2-1600x710.png" style="width: 100%;" /><p>2. Référencez la variable souhaitée dans la requête de visualisation.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/71cdf3e9df72c59d957628a3aa6e4aa9bd60d6d5-1600x676.png" style="width: 100%;" /><h2>Contrôles variables dans Discover</h2><p>Les contrôles variables ne sont pas seulement une fonctionnalité du tableau de bord. Ils sont également disponibles directement dans l&#x27;éditeur ES|QL de Discover. Vous pouvez créer des contrôles pour accélérer l&#x27;exploration des données dans Discover, les intégrer au tableau de bord et inversement.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fdddeec902d0bc746caed9276d01d7d48793dd85-1600x709.png" style="width: 100%;" /><h2>Détails techniques</h2><p>Vous avez probablement remarqué que les contrôles de variables sont assortis de quelques règles, telles que les éléments d&#x27;une requête auxquels ils peuvent faire référence et les préfixes que vous devez utiliser (« ?... » pour les valeurs et « ??... » pour les champs ou les fonctions). En effet, les variables ne sont pas de simples remplacements de chaînes effectués sur le client. Ce sont des éléments essentiels du langage de requête (connus sous le nom de <a href="https://www.elastic.co/docs/solutions/search/agent-builder/tools/esql-tools#parameter-types">paramètres dans ES|QL</a>).</p><p></p><p>Cette conception apporte de grands avantages. D&#x27;une part, Kibana peut comprendre le contexte de chaque variable, ce qui nous permet de générer et de pré-remplir automatiquement sa configuration pour vous. C’est aussi beaucoup plus sécurisé : le langage valide strictement les entrées variables et empêche ainsi les injections malveillantes tout en signalant toute erreur. De plus, il améliore les performances et la stabilité en transférant la validation complexe et la gestion des erreurs vers le serveur plutôt que vers le client. Une note sur les performances : une bonne pratique consiste à créer des variables qui incluent des requêtes rapides, car elles se chargent avant le tableau de bord. Ainsi, des requêtes lentes peuvent affecter les performances globales du tableau de bord.</p><p>Bien sûr, cette architecture présente aussi quelques <a href="https://www.elastic.co/docs/solutions/search/agent-builder/limitations-known-issues#esql-limitations">limites</a>, pour le moment. Les variables ne prennent pas encore en charge l&#x27;option « Tout » pour le filtrage, et elles ne peuvent actuellement pas être utilisées avec certains opérateurs tels que <code>LIKE</code><strong> </strong>ou <code>FROM</code> (pour changer de source de données). La bonne nouvelle ? Nous travaillons activement à l&#x27;ajout de ces fonctionnalités.</p><h2>Ce que l&#x27;avenir réserve aux contrôles</h2><p>Nous ne nous arrêtons pas là ! Parmi les améliorations que nous suivons de près, citons :</p><p>✨ La possibilité de placer des contrôles n&#x27;importe où sur le tableau de bord</p><p>✨ Chaînage de vos contrôles, ce qui signifie que la sortie d&#x27;un contrôle devient l&#x27;entrée du suivant</p><p>✨ De meilleures options de sélection comme la sélection « Tout » pour les variables</p><p>✨ Nouveaux types de contrôle (contrôle de type rechercher et variables pour vos sources de données)</p><p>✨ Et d&#x27;autres améliorations de l&#x27;expérience utilisateur que vous avez demandées, comme le pré-filtrage des contrôles normaux</p><p>Si vous avez des idées ou des commentaires, n&#x27;hésitez pas à nous en faire part.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Hegarty, Hemant Malik, Corey Nolet, Manas Singh, Mithun Radhakrishnan, Mayya Sharipova, Lorenzo Dematte, Ben Frederickson]]></dc:creator>
            <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/08f7469a4daaf67b7c5999585aae179b6680c78d-896x746.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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&#x27;accélération GPU à Elasticsearch, en intégrant <a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>—comme détaillé lors d&#x27;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&#x27;équipe de recherche vectorielle de NVIDIA.</p><h2>Récapitulatif</h2><p>Tout d&#x27;abord, faisons le point sur la situation. Elasticsearch s&#x27;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&#x27;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&#x27;efficacité de l&#x27;indexation vectorielle afin de mieux prendre en charge les charges de travail vectorielles à grande échelle.</p><h2>Le défi</h2><p>L&#x27;un des défis les plus complexes dans la création d&#x27;une base de données vectorielle haute performance est la construction de l&#x27;index vectoriel, le graphe <a href="https://arxiv.org/abs/1603.09320">HNSW</a>. La construction de l&#x27;index est rapidement dominée par des millions, voire des milliards d&#x27;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&#x27;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&#x27;index et une récupération d&#x27;embeddings pour les charges de travail liées à l&#x27;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&#x27;<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&#x27;une manière idiomatique Java, tout en restant moderne et performante.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5f6daa1eb07f704a6707d9e6b7ccb81d0abaa8c9-566x419.png" style="width: 100%;" /><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&#x27;indexation vectorielle sur le GPU peut être effectuée sur le même node et processus Elasticsearch, sans qu&#x27;il soit nécessaire de provisionner du code ou du matériel externe. Lors de la création d&#x27;index, si la bibliothèque cuVS est installée et qu&#x27;un GPU est présent et configuré, Elasticsearch utilisera le GPU pour accélérer le processus d&#x27;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&#x27;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&#x27;autres tâches (recherche simultanée, traitement des données, etc.).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3ea255bd9bfd7983f78143c5eba999d2149d72be-671x356.png" style="width: 100%;" /><h2>Accélération de la création d&#x27;index</h2><p>Dans le cadre de l&#x27;intégration de l&#x27;accélération GPU dans Elasticsearch, plusieurs améliorations ont été apportées à cuvs-java, en mettant l&#x27;accent sur l&#x27;efficacité de l&#x27;entrée/sortie de données et l&#x27;invocation de fonctions. L&#x27;une des principales améliorations est l&#x27;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&#x27;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&#x27;indexation, les vecteurs sont d&#x27;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&#x27;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&#x27;adapte également facilement à différentes largeurs de bits, telles que float32 ou int8, et s&#x27;étend naturellement à d&#x27;autres schémas de quantification.</p><h2>Roulement de tambour... alors, comment ça fonctionne ?</h2><p>Avant d&#x27;examiner les chiffres, un peu de contexte s&#x27;impose. La fusion des segments dans Elasticsearch s&#x27;exécute généralement automatiquement en arrière-plan pendant l&#x27;indexation, ce qui rend difficile l&#x27;é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&#x27;indicateur utile des améliorations attendues, même si les gains exacts peuvent différer selon les charges de travail d&#x27;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&#x27;Elasticsearch a été configuré pour utiliser le nombre optimal par défaut de threads d&#x27;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&#x27;ensemble de données, nous avons utilisé 2,6 millions de vecteurs avec 1 536 dimensions provenant de l&#x27; <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&#x27;à 95 %. Voici nos conclusions :</p><ul><li><strong>Débit d&#x27;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.</li><li><strong>Fusion forcée :</strong> une fois l&#x27;indexation terminée, le GPU continue d&#x27;accélérer la fusion des segments, multipliant par environ 7 la vitesse de la phase de fusion forcée.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f60ea9ee5996e456f393ffd195ee7eada6e5a7c2-948x387.png" style="width: 100%;" /><ul><li><strong>Utilisation du processeur :</strong> le transfert de la construction du graphe vers le GPU réduit considérablement l&#x27;utilisation moyenne et maximale du processeur. Les graphiques ci-dessous illustrent l&#x27;utilisation du CPU pendant l&#x27;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&#x27;utilisation du CPU pendant l&#x27;indexation GPU permet de libérer des cycles CPU qui peuvent être réaffectés à l&#x27;amélioration des performances de recherche.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5e680a5fc41700a877f3d8b2e5ce18ebd3f37a0b-1600x562.png" style="width: 100%;" /><ul><li><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.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/48a2b7758606bd321712b7d8378cd2640e652a4e-1384x544.png" style="width: 100%;" /><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&#x27;utilisation ou non du GPU lors de l&#x27;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&#x27;é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&#x27;accélération GPU et l&#x27;indexation uniquement sur processeur.</p><p>L&#x27;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&#x27;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&#x27;amélioration dans le débit d&#x27;indexation, et <strong>~6x </strong>dans la fusion forcée, tout en construisant des graphes qui atteignent des niveaux de rappel allant jusqu&#x27;à <strong>95%.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8ffa58cae3ad175ef2932a351aeef4c34a1407b9-948x394.png" style="width: 100%;" /><h2>Conclusion</h2><p>Dans les scénarios de bout en bout, l&#x27;accélération GPU avec NVIDIA cuVS permet d&#x27;améliorer de près de 12 fois le débit d&#x27;indexation et de réduire de 7 fois la latence de fusion forcée, tout en diminuant considérablement l&#x27;utilisation du processeur. Cela démontre que l&#x27;indexation vectorielle et les charges de travail de fusion bénéficient considérablement de l&#x27;accélération GPU. Sur une comparaison ajustée en fonction des coûts, l&#x27;accélération GPU continue d&#x27;offrir des gains de performances substantiels, avec un débit d&#x27;indexation environ 5 fois supérieur et des opérations de fusion forcée 6 fois plus rapides.</p><p>L&#x27;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&#x27;informations à venir.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ES|QL dans la version 9.2 : jointures Smart Lookup et prise en charge des séries temporelles]]></title>
            <description><![CDATA[Découvrez trois nouveautés d’ES|QL dans Elasticsearch 9.2 : une jointure LOOKUP optimisée pour une corrélation de données plus expressive, la nouvelle commande TS pour l’analyse des séries temporelles, et la commande flexible INLINE STATS pour l’agrégation.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/esql-elasticsearch-9-2-multi-field-joins-ts-command</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/esql-elasticsearch-9-2-multi-field-joins-ts-command</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tyler Perkins, Kostas Krikellas, Julian Kiryakov]]></dc:creator>
            <pubDate>Tue, 02 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/415894e21e7758c907d6e60d4efc94230349beef-2012x1164.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch 9.2, sorti en octobre, est doté d&#x27;avancées significatives qui rendent l&#x27;analyse de vos données plus rapide, plus flexible et plus accessible que jamais. Au cœur de cette version se trouvent d&#x27;importantes améliorations apportées à ES|QL, notre langage de requêtes canalisées, conçu pour apporter encore plus de valeur directement aux utilisateurs finaux.</p><p>Découvrez les fonctionnalités d’Elasticsearch 9.2 qui révolutionneront votre analyse de données avec ES|QL.</p><h2>Révolutionner la corrélation des données : un Lookup Join plus intelligent, plus rapide et plus flexible</h2><p>La commande <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join">LOOKUP JOIN</a> dans ES|QL a subi une transformation importante dans Elasticsearch 9.2, elle devient considérablement plus efficace et polyvalente. LOOKUP JOIN fusionne les données issues de la table de résultats de votre requête ES|QL avec les enregistrements concordants d’un index de mode de recherche que vous avez désigné. Cela permet d’ajouter des champs de l’index de recherche en tant que nouvelles colonnes à votre table de résultats, en faisant correspondre les valeurs du champ de jointure. Auparavant, la jointure des données était limitée à un seul champ et à une simple égalité. Plus maintenant ! Ces améliorations vous permettent de gérer facilement des scénarios complexes de corrélation de données.</p><p><strong>Les principales améliorations de Lookup Join incluent :</strong></p><ul><li><strong>Jointures multi-champs :</strong> Effectuez facilement des jointures sur plusieurs champs. Par exemple, pour joindre <code>application_logs</code> à <code>service_registry</code> sur <code>service_name</code>, <code>environment</code> et <code>version:</code></li></ul><pre><code>FROM application_logs
| LOOKUP JOIN service_registry ON service_name, environment, version</code></pre><ul><li><strong>Utilisation d’expressions pour des prédicats de jointure complexes (aperçu technologique) :</strong></li></ul><p>Vous n&#x27;êtes plus limité à la simple égalité. LOOKUP JOIN permet désormais de spécifier <strong>plusieurs critères</strong> de corrélation et d’incorporer une gamme <strong>d’opérateurs binaires,</strong> notamment ==, !=, &lt;, &gt;, &lt;=et &gt;=. Cela signifie que vous pouvez créer des conditions de jointure très nuancées, vous permettant de poser des questions beaucoup plus sophistiquées à vos données.</p><p>Exemple 1 : Recherche des métriques d’application avec un seuil de SLA par service</p><pre><code>FROM application_metrics
| LOOKUP JOIN sla_thresholds
      ON service_name == sla_service AND response_time > sla_response_time</code></pre><p>Exemple 2 : Cette requête calcule le montant dû, en se basant sur les politiques tarifaires régionales qui évoluent au fil du temps. Cela relie trois ensembles de données basés sur des périodes complexes et des conditions d’égalité pour calculer un <code>due_amount</code>final. La deuxième jonction de recherche utilise le champ <code>measurement_date</code> de l’indice <code>meter_readings</code> et le champ <code>region_id</code> de l’indice <code>customers</code> pour joindre l’indice <code>pricing_policies</code> et trouver la politique de tarification appropriée pour le <code>region</code> et le <code>measurement_date</code> particuliers.</p><pre><code>FROM meter_readings
| LOOKUP JOIN customers
      ON meter_id
| LOOKUP JOIN pricing_policies
      ON
        region_id == region AND
          measurement_date >= policy_begin_date AND
          measurement_date < policy_end_date
| EVAL due_amount = (kwh_consumed * rate_per_kwh + base_charge) * (1 + tax_rate)
| EVAL period = policy_name
| KEEP customer_name, period, due_amount, measurement_date, kwh_consumed,
    rate_per_kwh, base_charge, tax_rate
| SORT measurement_date</code></pre><ul><li><strong>Des gains de performances considérables pour les jointures filtrées : </strong></li></ul><p>Nous avons amélioré les performances des « jointures en expansion » qui sont filtrées à l&#x27;aide de conditions de table de recherche. Les jointures en expansion produisent plusieurs correspondances par ligne d&#x27;entrée, ce qui peut créer de grands ensembles de résultats intermédiaires. Cela s’aggrave lorsqu’un filtre ultérieur écarte un grand nombre de ces lignes. Avec la version 9.2, nous optimisons ces jointures en excluant les lignes superflues lorsqu’un filtre est appliqué aux données de recherche, ce qui permet d’éviter de traiter des lignes vouées à être éliminées. Dans certains scénarios, ces jointures peuvent être jusqu&#x27;à <strong>1000 fois plus rapides</strong> !</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/002c014ee29b1aaf9ddeb8c554bb76efe3ed180c-1572x954.png" style="width: 100%;" /><p>Cette optimisation est déterminante pour gérer les « jointures à résultats multiples », là où une consultation initiale peut produire de nombreux résultats potentiels. La transmission intelligente des filtres garantit que seules les données pertinentes sont traitées, réduisant ainsi fortement le temps d’exécution des requêtes pour une analyse en temps réel sur des ensembles de données gigantesques. Cela signifie que vous obtenez vos informations beaucoup plus rapidement, même avec des opérations de jointure très volumineuses ou complexes.</p><p><strong>Recherchez la compatibilité de Lookup Join avec la rechercher inter-clusters (CCS) :</strong></p><p>Lorsque Lookup Join est devenu disponible en version générale dans les versions 8.19 et 9.1, il manquait le support de la recherche inter-clusters (CCS). LOOKUP JOIN s’intègre parfaitement à CCS en 9.2, ce qui est un atout majeur pour les entreprises gérant plusieurs clusters. Placez simplement votre index de recherche sur tous les clusters distants où vous souhaitez effectuer une jointure, et ES|QL utilisera automatiquement ces index de recherche distants pour la jointure avec vos données distantes. Cela simplifie l&#x27;analyse distribuée des données et garantit un enrichissement constant sur l&#x27;ensemble de votre déploiement Elasticsearch.</p><p>Ces améliorations vous permettent de corréler divers ensembles de données avec une précision, une rapidité et une facilité sans précédent, afin de découvrir des informations plus approfondies et plus exploitables sans solutions complexes ni étapes de prétraitement.</p><h2>Enrichissez vos données en toute simplicité : Kibana Discover UX pour les index de recherche</h2><p>L’enrichissement des données doit rester simple, et non constituer un problème. Une fantastique nouvelle expérience utilisateur a été ajoutée à Discover (Kibana) pour créer et gérer les index de recherche.</p><p><strong>Workflow intuitif : la fonction de</strong> saisie semi-automatique complète de Discover vous guidera tout au long du processus, en suggérant des index de recherche et des champs de jointure dans l’éditeur ES|QL, ce qui facilite grandement la connexion de vos données téléchargées avec les index existants. Tapez le nom d&#x27;un index de recherche qui n&#x27;existe pas et accédez directement à l&#x27;éditeur de recherche en un seul clic pour créer l&#x27;index. Tapez le nom d&#x27;un index de recherche existant, et nous vous suggérerons une option pour le modifier :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d36fd6ffd6bc0bf8d31067f6266445c68d15c71c-1400x184.png" style="width: 100%;" /><p><strong>Gestion en ligne (CRUD) :</strong> maintenez vos ensembles de données de référence à jour grâce à des fonctionnalités d&#x27;édition en ligne (création, lecture, mise à jour, suppression) directement dans Discover.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a0e4aa66540b1f725c24ccb0519d978415073bb6-1453x842.png" style="width: 100%;" /><p><strong>Téléchargement de fichiers sans effort : </strong>vous pouvez désormais télécharger directement des fichiers, tels que des CSV, dans Discover et les utiliser instantanément dans vos <code>LOOKUP JOIN</code>. Plus besoin de changer constamment de contexte en naviguant entre les différentes zones de Kibana !</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0f5dc2c712af4c4cada50292a7c8b836eb02aa67-1600x748.png" style="width: 100%;" /><p>Cette fonctionnalité démocratise l’enrichissement des données, que vous mappiez des ID utilisateur à des noms, ajoutiez des métadonnées d’entreprise ou joigniez des fichiers de référence statiques. La puissance des jointures est désormais à la portée de tous, de manière rapide, simple et au même endroit.</p><h2>Préservez votre contexte : présentation d&#x27;INLINE STATS (aperçu technique)</h2><p>L&#x27;agrégation des données est cruciale, mais parfois vous avez besoin de voir les agrégations <em>à côté de</em> vos données originales. Nous sommes ravis de présenter les <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by">STATS EN LIGNE</a> en tant que fonctionnalité de la <strong>Tech Preview.</strong></p><p>Contrairement à la commande <code>STATS</code>, qui remplace vos champs d&#x27;entrée par une sortie agrégée, <code>INLINE STATS</code> préserve tous vos champs d&#x27;entrée d&#x27;origine et ajoute simplement les nouveaux champs agrégés. Cela vous permet d’effectuer des opérations supplémentaires sur vos champs d’entrée originaux <em>après</em> l’agrégation, offrant ainsi un workflow d’analyse plus continu et plus flexible.</p><p>Par exemple, pour calculer la distance moyenne des vols tout en conservant les lignes de vol individuelles :</p><pre><code>FROM kibana_sample_data_flights
 | KEEP Carrier, Dest, DistanceMiles
 | INLINE STATS avgDist = ROUND(AVG(DistanceMiles))
       BY Dest
 | WHERE DistanceMiles > avgDist</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6eea943035e0ab371270084c504a06bb89f8b82b-1496x290.png" style="width: 100%;" /><p>Dans cette requête, <code>avgDist</code> est ajouté à chaque ligne avec le <code>Dest</code>correspondant (ination) par lequel nous avons regroupé, puis, comme nous avons toujours les colonnes d’information de vol, nous pouvons filtrer les résultats vers les vols ayant une distance supérieure à la moyenne.</p><h2>Prise en charge des séries temporelles dans ES|QL (aperçu technique)</h2><p>Elasticsearch utilise des <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">flux de données temporelles</a> pour stocker des métriques. Nous ajoutons la prise en charge des agrégations de séries temporelles dans ES|QL, via la commande source <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a> . Cette fonctionnalité est disponible dans Elastic Cloud serverless et en version 9.2 (niveau Basic) en préversion technique.</p><p>L’analyse de séries temporelles est principalement basée sur des requêtes d’agrégation qui résument les valeurs de métriques sur des plages de temps, segmentées par une ou plusieurs dimensions de filtrage. La majorité des requêtes d’agrégation nécessitent un traitement en deux étapes, avec (a) une fonction d’agrégation interne qui résume les valeurs de chaque série temporelle, et (b) une fonction d’agrégation externe qui consolide les résultats de (a) au travers des séries temporelles.</p><p>La commande source <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a>, combinée à <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS</code></a>, offre un moyen concis et efficace d&#x27;exprimer de telles requêtes sur des séries temporelles. Plus concrètement, considérons l’exemple suivant pour calculer le taux total de requêtes par hôte et par heure :</p><pre><code>TS my_metrics
| WHERE @timestamp > NOW() - 1 day
| STATS SUM(RATE(requests))
      BY host, TBUCKET(1h)</code></pre><p>Dans ce cas, la fonction d&#x27;agrégation de séries chronologiques <code>RATE</code> est d&#x27;abord évaluée par série chronologique et par heure. Les agrégats partiels produits sont ensuite combinés à l&#x27;aide de <code>SUM</code> pour calculer les valeurs agrégées finales par hôte et par heure.</p><p>Vous pouvez consulter la liste des fonctions d&#x27;agrégation de séries temporelles disponibles <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions">ici</a>. La fonction <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-metric">counter_rate</a> est désormais prise en charge, sans doute la fonction d’agrégation la plus importante pour le traitement des compteurs.</p><p>La commande source <code>TS</code> est conçue pour être combinée avec <code>STATS</code>, avec une exécution adaptée pour prendre en charge efficacement les agrégations de séries temporelles. Par exemple, les données sont triées avant d’entrer dans le <code>STATS</code>. Les commandes de traitement susceptibles d’enrichir ou de modifier les données temporelles ou leur ordre, telles que <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork"><code>FORK</code></a> ou <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by"><code>INLINE STATS</code></a>, ne sont actuellement pas autorisées entre <code>TS</code> et <code>STATS</code>. Cette restriction pourrait être levée à l’avenir.</p><p>La sortie tabulaire de <code>STATS</code> peut être traitée avec n&#x27;importe quelle commande applicable. Par exemple, la requête suivante calcule le rapport entre la valeur moyenne de <code>cpu_usage</code> hébergé par hôte et par heure et la valeur maximale par hôte :</p><pre><code>TS my_metrics
| STATS avg_usage = AVG(AVG_OVER_TIME(cpu_usage))
      BY host, time_bucket = TBUCKET(1h)
| INLINE STATS max_avg_usage = MAX(avg_usage)
      BY host
| EVAL ratio = avg_usage / max_avg_usage
| KEEP host, time_bucket, ratio
| SORT host, time_bucket DESC</code></pre><p>Les données de séries temporelles sont enregistrées sur notre moteur de stockage sous-jacent à colonnes, optimisé par les doc values de Lucene. La commande TS ajoute une exécution vectorisée de requêtes via le moteur de calcul ES|QL. Les performances des requêtes sont souvent améliorées de plus d&#x27;un ordre de grandeur, par rapport aux requêtes <a href="https://www.elastic.co/docs/reference/query-languages/querydsl">DSL</a> équivalentes, et sont comparables aux systèmes établis spécifiques aux métriques. Une analyse détaillée de l’architecture et des performances sera bientôt disponible. Ne manquez pas cette publication.</p><h2>Élargir votre boîte à outils : nouvelles fonctions ES|QL</h2><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p><strong>Manipulation de chaînes : </strong><a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-contains">CONTAINS</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/mv-functions#esql-mv_contains">MV_CONTAINS</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_encode">URL_ENCODE</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_encode_component">URL_ENCODE_COMPONENT</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_decode">URL_DECODE</a> pour un traitement plus robuste du texte et des URL.</p><p><strong>Séries temporelles et géospatial :</strong> <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/grouping-functions#esql-tbucket">TBUCKET</a> pour des buckets temporels flexibles, TO_DENSE_VECTOR pour les opérations vectorielles, et un ensemble complet de <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/spatial-functions">fonctions géospatiales</a> comme <code>ST_GEOHASH</code>, <code>ST_GEOTILE</code>, <code>ST_GEOHEX</code>, <code>TO_GEOHASH</code>, <code>TO_GEOTILE</code>, <code>TO_GEOHEX</code> pour une analyse avancée basée sur la localisation.</p><p><strong>Formatage de la date :</strong> <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/date-time-functions#esql-day_name">DAY_NAME</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/date-time-functions#esql-month_name">MONTH_NAME</a> pour une représentation plus lisible des dates.</p><p>Ces fonctions vous offrent un ensemble d&#x27;outils plus riche pour manipuler et analyser vos données directement dans ES|QL.</p><h2>Sous le capot : plus de performance et d&#x27;efficacité</h2><p>Au-delà des fonctionnalités mises en avant, Elasticsearch 9.2 inclut de nombreuses optimisations de performances pour ES|QL. Nous avons accéléré <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/where#like-and-rlike">RLIKE (LIST</a>) avec pushdown dans les cas où la fonction remplace plusieurs requêtes RLIKE similaires. Avec <code>RLIKE</code> (LIST), nous pouvons fusionner ces requêtes en un seul automate et appliquer un automate au lieu de plusieurs. Nous avons également accéléré le chargement des champs de mots clés grâce à des tris d&#x27;index et des optimisations générales des requêtes. Ces améliorations garantissent que vos requêtes ES|QL s&#x27;exécutent plus efficacement que jamais.</p><h2>Lancez-vous dès aujourd&#x27;hui !</h2><p>La version 9.2 d’Elasticsearch représente une avancée majeure pour ES|QL, conférant une puissance et une flexibilité inédites à vos workflows d’analyse de données. Nous vous encourageons à explorer ces nouvelles fonctionnalités et à constater la différence qu’elles apportent.</p><p>Pour une liste complète de tous les changements et améliorations dans Elasticsearch 9.2, veuillez consulter les <a href="https://www.elastic.co/guide/en/elasticsearch/reference/9.2/release-notes-9.2.0.html">notes de publication officielles</a>. Bonne recherche !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Créer un connecteur ChatGPT avec Elasticsearch pour interroger les issues GitHub]]></title>
            <description><![CDATA[Découvrez comment créer un connecteur ChatGPT personnalisé et déployer un serveur MCP Elasticsearch qui utilise la rechercher hybride pour interroger les issues GitHub internes.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
            <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>OpenAI a récemment annoncé la fonctionnalité <a href="https://help.openai.com/en/articles/11487775-connectors-in-chatgpt">connecteurs personnalisés</a> pour ChatGPT sur les plans Pro/Business/Entreprise et Edu. En plus des connecteurs prêts à l&#x27;emploi pour accéder aux données sur Gmail, GitHub, Dropbox, etc., il est possible de créer des connecteurs personnalisés en utilisant des serveurs MCP.</p><p>Les connecteurs personnalisés vous donnent la possibilité de combiner vos connecteurs ChatGPT existants avec des sources de données supplémentaires comme Elasticsearch pour obtenir des réponses complètes.</p><p>Dans cet article, nous allons créer un serveur <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> qui connecte ChatGPT à un index Elasticsearch contenant des informations sur les issues GitHub internes et les requêtes pull. Cela permet de répondre aux requêtes en langage naturel en utilisant vos données Elasticsearch.</p><p>Nous déploierons le serveur MCP en utilisant <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a> sur Google Colab avec ngrok pour obtenir une URL publique à laquelle ChatGPT peut se connecter, éliminant ainsi le besoin d&#x27;une configuration complexe de l&#x27;infrastructure.</p><p>Pour un aperçu complet de MCP et de son écosystème, reportez-vous à la section <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">État actuel du MCP</a>.</p><h2>Prérequis</h2><p>Avant de commencer, vous aurez besoin des éléments suivants :</p><ul><li>Cluster Elasticsearch (8.X ou supérieur)</li><li>Clé API Elasticsearch avec accès en lecture à votre index</li><li>Compte Google (pour Google Colab)</li><li>Compte ngrok (fonctionne avec le niveau gratuit)</li><li>Compte ChatGPT avec un forfait Pro/Entreprise/Business ou Edu</li></ul><h2>Comprendre les exigences du connecteur ChatGPT MCP</h2><p>Les connecteurs ChatGPT MCP nécessitent l&#x27;implémentation de deux outils : <code>search</code> et <code>fetch</code>. Pour plus de détails, consultez <a href="https://platform.openai.com/docs/mcp#create-an-mcp-server">OpenAI Docs</a>.</p><h3><a href="https://platform.openai.com/docs/mcp#search-tool">Outil de recherche</a></h3><p>Renvoie une liste de résultats pertinents depuis votre index Elasticsearch en fonction d&#x27;une requête utilisateur.</p><h4>Ce qu&#x27;il reçoit :</h4><ul><li>Une chaîne unique contenant la requête en langage naturel de l&#x27;utilisateur.</li><li>Exemple : &quot;Recherchez les issues liées à la migration d&#x27;Elasticsearch.&quot;</li></ul><h4>Ce qu&#x27;il renvoie : </h4><ul><li>Un objet avec une clé <code>result</code> contenant un tableau d&#x27;objets de résultats. Chaque résultat inclut :<ul><li><code>id</code> - Identifiant de document unique</li><li><code>title</code> - Titre de l&#x27;issue ou de la PR</li><li><code>url</code> - Lien vers l&#x27;issue/la PR</li></ul></li></ul><h4>Dans notre implémentation :</h4><pre><code>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}</code></pre><h3><a href="https://platform.openai.com/docs/mcp#fetch-tool">Outil de récupération</a></h3><p>Récupère le contenu complet d&#x27;un document spécifique.</p><h4>Ce qu&#x27;il reçoit :</h4><ul><li>Chaîne unique contenant l&#x27;ID du document Elasticsearch extrait du résultat de la recherche</li><li>Exemple : &quot;Donnez-moi les détails de la PR-578.&quot;</li></ul><h4>Ce qu&#x27;il renvoie :</h4><ul><li>Objet de document complet contenant :<ul><li><code>id</code> - Identifiant de document unique</li><li><code>title</code> - Titre de l&#x27;issue ou de la PR</li><li><code>text</code> - Description complète du problème/PR et détails</li><li><code>url</code> - Lien vers l&#x27;issue/la PR</li><li><code>type</code> - Type de document (issue, pull_request)</li><li><code>status</code> - Statut actuel (ouvert, en cours, résolu)</li><li><code>priority</code> - Niveau de priorité (faible, moyen, élevé, critique)</li><li><code>assignee</code> - Personne en charge de l&#x27;issue/la PR</li><li><code>created_date</code> - Date de création</li><li><code>resolved_date</code> - Date de résolution (le cas échéant)</li><li><code>labels</code> - Balises associées au document</li><li><code>related_pr</code> - ID de la requête pull associée</li></ul></li></ul><pre><code>return {
    "id": "PR-578",
    "title": "Security hotfix: Patch SQL injection vulnerabilities",
    "text": "Description: CRITICAL SECURITY FIX for ISSUE-1889. Patches SQL...",
    "url": "https://internal-git.techcorp.com/pulls/578",
    "type": "pull_request",
    "status": "closed",
    "priority": "critical",
    "assignee": "sarah_dev",
    "created_date": "2025-09-19",
    "resolved_date": "2025-09-19",
    "labels": "security, hotfix, sql",
    "related_pr": null
}</code></pre><p><strong>Remarque</strong> : Cet exemple utilise une structure plate où tous les champs se trouvent au niveau racine. Les exigences d&#x27;OpenAI sont flexibles et prennent également en charge les objets de métadonnées imbriqués.</p><h2>Issues GitHub et ensemble de données de PR</h2><p>Pour ce tutoriel, nous allons utiliser un ensemble de données interne de GitHub contenant des issues et des requêtes pull. Ceci représente un scénario dans lequel vous souhaitez interroger des données privées et internes via ChatGPT.</p><p>L&#x27;ensemble de données est accessible <a href="https://gist.github.com/TomasMurua/4e7bbdf7a7ebbdffaa663c43578d934a">ici</a>. Et nous mettrons à jour l&#x27;index des données à l&#x27;aide de l&#x27;<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">API Bulk</a>.</p><p>Cet ensemble de données comprend :</p><ul><li>Issues avec description, état, niveau de priorité et personnes en charge</li><li>Requêtes pull avec modifications de code, révisions et informations de déploiement</li><li>Relations entre les issues et les PR (p. ex., la PR-578 corrige l&#x27;ISSUE-1889)</li><li>Étiquettes, dates et autres métadonnées</li></ul><h3>Mappings de l&#x27;index</h3><p>L&#x27;index utilise les <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">mappings</a> suivants pour prendre en charge la recherche hybride avec <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>. Le champ <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">text_semantic</a> est utilisé pour la recherche sémantique, tandis que les autres champs permettent la recherche par mot-clé.</p><pre><code>{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "text": {
        "type": "text"
      },
      "text_semantic": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "url": {
        "type": "keyword"
      },
      "type": {
        "type": "keyword"
      },
      "status": {
        "type": "keyword"
      },
      "priority": {
        "type": "keyword"
      },
      "assignee": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date",
        "format": "iso8601"
      },
      "resolved_date": {
        "type": "date",
        "format": "iso8601"
      },
      "labels": {
        "type": "keyword"
      },
      "related_pr": {
        "type": "keyword"
      }
    }
  }
}</code></pre><h2>Créer le serveur MCP</h2><p>Notre serveur MCP implémente deux outils conformes aux spécifications d&#x27;OpenAI qui utilisent la recherche hybride pour combiner la sémantique et la correspondance de texte pour de meilleurs résultats.</p><h3>Outil de recherche</h3><p>Utilise la recherche hybride avec <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a> (fusion des rangs réciproques) qui combine la recherche sémantique avec la correspondance de texte :</p><pre><code>@mcp.tool()
    async def search(query: str) -> Dict[str, List[Dict[str, Any]]]:
        """
        Search for internal issues and PRs using hybrid search (semantic + text with RRF).
        Returns list with id, title, and url per OpenAI spec.
        """
        if not query or not query.strip():
            return {"results": []}

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

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

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

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

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

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

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

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

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

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

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

        except Exception as e:
            logger.error(f"Fetch error: {e}")
            raise ValueError(f"Failed to fetch '{id}': {str(e)}")</code></pre><h3>Points clés :</h3><ul><li><strong>Recherche par champ d&#x27;ID de document</strong> : utilise une requête de terme sur le champ personnalisé <code>id</code></li><li><strong>Renvoie le document complet</strong> : inclut le champ complet <code>text</code> avec tout le contenu</li><li><strong>Structure plate</strong> : tous les champs au niveau racine, correspondant à la structure de document d&#x27;Elasticsearch.</li></ul><h2>Déployer sur Google Colab</h2><p>Nous utiliserons Google Colab pour exécuter notre serveur MCP et ngrok pour l&#x27;exposer publiquement afin que ChatGPT puisse s&#x27;y connecter.</p><h3>Étape 1 : Ouvrir le notebook Google Colab</h3><p>Accédez à notre notebook préconfiguré <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-chatgpt-connector">Elasticsearch MCP pour ChatGPT</a>.</p><h3>Étape 2 : Configurer vos identifiants</h3><p>Vous aurez besoin de trois informations :</p><ul><li><strong>URL Elasticsearch</strong> : l&#x27;<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/connect-elasticsearch">URL de votre cluster Elasticsearch</a>.</li><li><strong>Clé API Elasticsearch</strong> : <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">clé API</a> avec accès en lecture à votre index.</li><li><strong>Jeton d&#x27;authentification ngrok</strong> : jeton gratuit fourni par <a href="https://ngrok.com/">ngrok</a>. Nous utiliserons ngrok pour exposer l&#x27;URL du MCP à l&#x27;Internet afin que ChatGPT puisse s&#x27;y connecter.</li></ul><h4>Obtenir votre token ngrok</h4><ol><li>Créez un compte gratuit sur <a href="https://ngrok.com/">ngrok</a></li><li>Accédez à votre <a href="https://dashboard.ngrok.com/">tableau de bord ngrok</a></li><li>Copier votre jeton d&#x27;authentification</li></ol><h4>Ajouter des secrets à Google Colab</h4><p>Dans le notebook Google Colab :</p><ol><li>Cliquez sur l&#x27;<strong>icône clé</strong> dans la barre latérale gauche pour ouvrir <strong>Secrets</strong>.</li><li>Ajoutez ces trois secrets :</li></ol><pre><code>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token</code></pre><p>3. Activer l&#x27;accès aux notebooks pour chaque secret</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" style="width: 100%;" /><h3>Étape 3 : Exécuter le notebook</h3><ol><li>Cliquez sur <strong>Runtime</strong> (Exécution) puis sur <strong>Run all</strong> (Tout exécuter) pour exécuter toutes les cellules</li><li>Attendez que le serveur démarre (environ 30 secondes)</li><li>Recherchez l&#x27;URL publique de ngrok dans la sortie</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" style="width: 100%;" /><p>4. La sortie affichera quelque chose comme :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" style="width: 100%;" /><h2>Se connecter à ChatGPT</h2><p>Nous allons maintenant connecter le serveur MCP à votre compte ChatGPT.</p><ol><li>Ouvrez ChatGPT et accédez aux <strong>Paramètres</strong>.</li><li>Accédez à <strong>Connectors</strong> (Connecteurs).Si vous utilisez un compte Pro, vous devez activer le <a href="https://platform.openai.com/docs/guides/developer-mode">mode développeur</a> dans les connecteurs.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" style="width: 100%;" /><p><em>Si vous utilisez ChatGPT Enterprise ou Business, vous devez publier le connecteur sur votre espace de travail.</em></p><p>3. Cliquez sur <strong>Create</strong> (Créer).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" style="width: 100%;" /><p><em><strong>Remarque</strong> : Dans les espaces de travail Business, Entreprise et Edu, seuls les propriétaires, les administrateurs et les utilisateurs ayant activé l&#x27;option correspondante (pour Entreprise/Edu) peuvent ajouter des connecteurs personnalisés. Les utilisateurs ayant un rôle de membre standard ne peuvent pas ajouter de connecteurs personnalisés eux-mêmes.</em></p><p><em>Une fois qu&#x27;un connecteur est ajouté et activé par un propriétaire ou un utilisateur administrateur, il devient accessible à tous les membres de l&#x27;espace de travail.</em></p><p>4. Saisissez les informations requises et votre URL ngrok se terminant par <code>/sse/</code>. Notez le &quot;/&quot; après &quot;sse&quot;. Cela ne fonctionnera pas sans cet élément :</p><ul><li><strong>Nom :</strong> Elasticsearch MCP</li><li><strong>Description</strong> : MCP personnalisé pour la recherche et la récupération d&#x27;informations GitHub internes.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" style="width: 100%;" /><p>5. Appuyez sur <strong>Créer</strong> pour enregistrer le MCP personnalisé.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" style="width: 100%;" /><p>La connexion est instantanée si votre serveur est en cours d&#x27;exécution. Aucune authentification supplémentaire n&#x27;est requise, car la clé API Elasticsearch est configurée sur votre serveur.</p><h2>Tester le serveur MCP</h2><p>Avant de poser des questions, vous devez sélectionner le connecteur que ChatGPT doit utiliser.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" style="width: 100%;" /><h3>Prompt 1 : Recherchez les issues</h3><p>Demandez : &quot;<strong>Recherchez les issues liées à la migration d&#x27;Elasticsearch</strong>&quot;, puis confirmez l&#x27;appel à l&#x27;outil d&#x27;action.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" style="width: 100%;" /><p>ChatGPT appellera l&#x27;outil <code>search</code> avec votre requête. Vous pouvez voir qu&#x27;il recherche des outils disponibles et se prépare à appeler l&#x27;outil Elasticsearch, et confirme auprès de l&#x27;utilisateur avant de prendre toute action sur l&#x27;outil.</p><h4>Demande d&#x27;appel d&#x27;outil :</h4><pre><code>{
  "query": "Elasticsearch migration issues"
}</code></pre><h4>Réponse de l&#x27;outil :</h4><pre><code>{
  "results": [
    {
      "id": "PR-598",
      "title": "Elasticsearch 8.x migration - Application code changes",
      "url": "https://internal-git.techcorp.com/pulls/598"
    },
    {
      "id": "ISSUE-1712",
      "title": "Migrate from Elasticsearch 7.x to 8.x",
      "url": "https://internal-git.techcorp.com/issues/1712"
    },
    {
      "id": "RFC-045",
      "title": "Design Proposal: Microservices Migration Architecture",
      "url": "https://internal-git.techcorp.com/rfcs/045"
    }
    // ... 7 more results
  ]
}</code></pre><p>ChatGPT traite les résultats et les présente dans un format conversationnel naturel.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" style="width: 100%;" /><h3>En coulisses</h3><h4>Prompt : &quot;Recherchez les issues liées à la migration d&#x27;Elasticsearch&quot;</h4><p>1. Appels de ChatGPT <code>search(“Elasticsearch migration”)</code></p><p>2. Elasticsearch effectue une recherche hybride.</p><ul><li>La <strong>recherche sémantique</strong> comprend des concepts tels que &quot;mise à niveau&quot; et &quot;<em>compatibilité des versions</em>&quot;.</li><li>La <strong>recherche de texte</strong> trouve des correspondances exactes pour &quot;<em>Elasticsearch</em>&quot; et &quot;migration&quot;.</li><li><strong>RRF</strong> combine et classe les résultats des deux approches</li></ul><p>3. Renvoie les 10 événements les plus pertinents avec <code>id</code>, <code>title</code>, <code>url</code></p><p>4. ChatGPT identifie &quot;<em>ISSUE-1712: migrate from Elasticsearch 7.x to 8.x</em>&quot; (Migrer d&#x27;Elasticsearch 7.x vers 8.x) comme résultat le plus pertinent.</p><h3>Prompt 2 : Obtenez tous les détails</h3><p>Demandez : <strong><em>&quot;Donnez-moi les détails de l&#x27;ISSUE-1889&quot;</em></strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" style="width: 100%;" /><p>ChatGPT comprend que vous souhaitez obtenir des informations détaillées sur un problème spécifique, appelle l&#x27;outil <code>fetch</code> et confirme auprès de l&#x27;utilisateur avant d&#x27;entreprendre des actions sur l&#x27;outil.</p><h4>Demande d&#x27;appel d&#x27;outil :</h4><pre><code>{
  "id": "ISSUE-1889"
}</code></pre><h4>Réponse de l&#x27;outil :</h4><pre><code>{
  "id": "ISSUE-1889",
  "title": "SQL injection vulnerability in search endpoint",
  "text": "Description: Security audit identified SQL injection vulnerability in /api/v1/search endpoint. User input from query parameter is not properly sanitized before being used in raw SQL query. Severity: HIGH - Immediate action required Affected Code: - File: services/search/query_builder.py - Line: 145-152 - Issue: String concatenation used instead of parameterized queries Investigation: - @security_team_alice: Confirmed exploitable with UNION-based injection - @sarah_dev: Checking all other endpoints for similar patterns - @john_backend: Found 3 more instances in legacy codebase Remediation: - Rewrite using SQLAlchemy ORM or parameterized queries - Add input validation and sanitization - Implement WAF rules as additional layer - Security regression tests Comments: - @tech_lead_mike: Stop all other work, this is P0 - @sarah_dev: PR-578 ready with fixes for all 4 vulnerable endpoints - @alex_devops: Deployed hotfix to production 2025-09-19 at 14:30 UTC - @security_team_alice: Verified fix, conducting full pentest next week Resolution: All vulnerable endpoints patched. Added pre-commit hooks to catch raw SQL queries. Security training scheduled for team.",
  "url": "https://internal-git.techcorp.com/issues/1889",
  "type": "issue",
  "status": "closed",
  "priority": "critical",
  "assignee": "sarah_dev",
  "created_date": "2025-09-18",
  "resolved_date": "2025-09-19",
  "labels": "security, vulnerability, bug, sql",
  "related_pr": "PR-578"
}</code></pre><p>ChatGPT synthétise les informations et les présente clairement.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" style="width: 100%;" /><h3>En coulisses</h3><h4>Prompt : &quot;Donnez-moi les détails de l&#x27;ISSUE-1889&quot;</h4><ol><li>Appels ChatGPT <code>fetch(“ISSUE-1889”)</code></li><li>Elasticsearch extrait le document complet</li><li>Retourne un document complet avec tous les champs au niveau racine</li><li>ChatGPT synthétise les informations et répond avec les citations appropriées.</li></ol><h2>Conclusion</h2><p>Dans cet article, nous avons créé un serveur MCP personnalisé qui connecte ChatGPT à Elasticsearch à l&#x27;aide d&#x27;outils MCP de <strong>recherche</strong> et de <strong>récupération</strong> dédiés, permettant de lancer des requêtes en langage naturel sur des données privées.</p><p>Ce modèle MCP fonctionne pour n&#x27;importe quel index Elasticsearch, documentation, produit, log ou toute autre donnée que vous souhaitez interroger en langage naturel.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Développement d'un assistant RAG agentique utilisant LangChain et Elasticsearch]]></title>
            <description><![CDATA[Apprenez à construire un assistant de presse agentique utilisant LangChain et Elasticsearch qui répond aux requêtes sur les articles avec un routage adaptatif.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/agentic-rag-news-assistant-langchain-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/agentic-rag-news-assistant-langchain-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Kirti Sodhi]]></dc:creator>
            <pubDate>Fri, 28 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d7213956309c6b5863627aca2ba95569b02fc4c7-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Cet article de blog se penche sur les flux de travail agentiques RAG, en expliquant leurs principales caractéristiques et les modèles de conception courants. Il démontre en outre comment mettre en œuvre ces flux de travail au moyen d&#x27;un exemple pratique qui utilise Elasticsearch comme magasin de vecteurs et LangChain pour construire le cadre agentique RAG. Enfin, l&#x27;article aborde brièvement les meilleures pratiques et les défis associés à la conception et à la mise en œuvre de ces architectures. Vous pouvez suivre la création d&#x27;un simple pipeline RAG agentique avec ce <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">carnet Jupyter</a>.</p><h2>Introduction au RAG agentique</h2><p>La Génération Augmentée de Récupération<a href="https://www.elastic.co/docs/solutions/search/rag">(RAG)</a> est devenue la pierre angulaire des applications basées sur le LLM, permettant aux modèles de fournir des réponses optimales en récupérant le contexte pertinent basé sur les requêtes de l&#x27;utilisateur. Les systèmes RAG améliorent la précision et le contexte des réponses LLM en s&#x27;appuyant sur des informations externes provenant d&#x27;API ou de magasins de données, au lieu d&#x27;être limités à des connaissances LLM préformées. D&#x27;autre part, les agents d&#x27;intelligence artificielle fonctionnent de manière autonome, prenant des décisions et des mesures pour atteindre les objectifs qui leur sont assignés.</p><p>Le RAG agentique est un cadre qui unifie les forces de la génération augmentée par la recherche et du raisonnement agentique. Il intègre le RAG dans le processus décisionnel de l&#x27;agent, ce qui permet au système de choisir dynamiquement les sources de données, d&#x27;affiner les requêtes pour une meilleure récupération du contexte, de générer des réponses plus précises et d&#x27;appliquer une boucle de rétroaction pour améliorer continuellement la qualité des résultats.</p><h2>Principales caractéristiques du RAG agentic</h2><p>Le cadre agentique des RAG constitue une avancée majeure par rapport aux systèmes traditionnels de RAG. Au lieu de suivre un processus de recherche fixe, il s&#x27;appuie sur des agents dynamiques capables de planifier, d&#x27;exécuter et d&#x27;optimiser les résultats en temps réel.</p><p>Examinons quelques-unes des principales caractéristiques qui distinguent les pipelines RAG agentiques :</p><ul><li><strong>Prise de décision dynamique</strong>: Le RAG agentique utilise un mécanisme de raisonnement pour comprendre l&#x27;intention de l&#x27;utilisateur et acheminer chaque requête vers la source de données la plus pertinente, produisant ainsi des réponses précises et adaptées au contexte.</li><li><strong>Analyse complète des requêtes :</strong> Agentic RAG analyse en profondeur les requêtes des utilisateurs, y compris les sous-questions et leur intention générale. Il évalue la complexité des requêtes et sélectionne de manière dynamique les sources de données les plus pertinentes pour récupérer les informations, garantissant ainsi des réponses précises et complètes.</li><li><strong>Collaboration en plusieurs étapes</strong>: Ce cadre permet une collaboration en plusieurs étapes grâce à un réseau d&#x27;agents spécialisés. Chaque agent s&#x27;occupe d&#x27;une partie spécifique d&#x27;un objectif plus large, travaillant de manière séquentielle ou simultanée pour atteindre un résultat cohérent.</li><li><strong>Mécanismes d&#x27;auto-évaluation</strong>: Le pipeline RAG agentique utilise l&#x27;autoréflexion pour évaluer les documents récupérés et les réponses générées. Il peut vérifier si les informations extraites répondent entièrement à la requête, puis vérifier l&#x27;exactitude, l&#x27;exhaustivité et la cohérence factuelle des résultats.</li><li><strong>Intégration avec des outils externes</strong>: Ce flux de travail peut interagir avec des API externes, des bases de données et des sources d&#x27;information en temps réel, en incorporant des informations actualisées et en s&#x27;adaptant dynamiquement à l&#x27;évolution des données.</li></ul><h2>Modèles de flux de travail des RAG agentiques</h2><p>Les modèles de flux de travail définissent la manière dont l&#x27;IA agentique structure, gère et orchestre les applications basées sur le LLM de manière fiable et efficace. Plusieurs cadres et plateformes, tels que <a href="https://www.langchain.com/">LangChain</a>, <a href="https://www.langchain.com/langgraph">LangGraph</a>, <a href="https://www.crewai.com/">CrewAI</a> et <a href="https://www.llamaindex.ai/">LlamaIndex</a>, peuvent être utilisés pour mettre en œuvre ces flux de travail agentiques.</p><ol><li><strong>Chaîne de récupération séquentielle</strong>: Les flux de travail séquentiels divisent les tâches complexes en étapes simples et ordonnées. Chaque étape améliore les données de l&#x27;étape suivante, ce qui permet d&#x27;obtenir de meilleurs résultats. Par exemple, lors de la création d&#x27;un profil de client, un agent peut extraire les détails de base d&#x27;un CRM, un autre récupère l&#x27;historique des achats dans une base de données de transactions, et un dernier agent combine ces informations pour générer un profil complet en vue de recommandations ou de rapports.</li><li><strong>Chaîne de recherche de routage</strong>: Dans ce modèle de flux de travail, un agent routeur analyse l&#x27;entrée et la dirige vers le processus ou la source de données la plus appropriée. Cette approche est particulièrement efficace lorsqu&#x27;il existe plusieurs sources de données distinctes se chevauchant très peu. Par exemple, dans un système de service à la clientèle, l&#x27;agent de routage classe les demandes entrantes, telles que les problèmes techniques, les remboursements ou les réclamations, et les achemine vers le service approprié pour un traitement efficace.</li><li><strong>Chaîne de recherche parallèle</strong>: Dans ce modèle de flux de travail, plusieurs sous-tâches indépendantes sont exécutées simultanément et leurs résultats sont ensuite agrégés pour générer une réponse finale. Cette approche permet de réduire considérablement le temps de traitement et d&#x27;accroître l&#x27;efficacité du flux de travail. Par exemple, dans un flux de travail parallèle de service à la clientèle, un agent récupère les demandes antérieures similaires et un autre consulte les articles pertinents de la base de connaissances. Un agrégateur combine ensuite ces résultats pour produire une résolution complète.</li><li><strong>Chaîne de travail de l&#x27;Orchestrator</strong>: Ce flux de travail présente des similitudes avec la parallélisation en raison de l&#x27;utilisation de sous-tâches indépendantes. Cependant, une distinction essentielle réside dans l&#x27;intégration d&#x27;un agent orchestrateur. Cet agent est chargé d&#x27;analyser les requêtes des utilisateurs, de les segmenter dynamiquement en sous-tâches au cours de l&#x27;exécution et d&#x27;identifier les processus ou outils appropriés nécessaires pour formuler une réponse précise.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ece6fc2403f211556c93e99d5227bfb7053b0c31-1600x1047.png" style="width: 100%;" /><h2>Construire un pipeline RAG agentique à partir de zéro</h2><p>Pour illustrer les principes du RAG agentique, concevons un flux de travail utilisant LangChain et Elasticsearch. Ce flux de travail adopte une architecture basée sur le routage, où plusieurs agents collaborent pour analyser les requêtes, récupérer les informations pertinentes, évaluer les résultats et générer des réponses cohérentes. Vous pouvez vous référer à ce <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">carnet Jupyter</a> pour suivre cet exemple.</p><p>Le flux de travail commence par l&#x27;agent routeur, qui analyse la requête de l&#x27;utilisateur pour sélectionner la méthode de recherche optimale, c&#x27;est-à-dire l&#x27;approche <code>vectorstore</code>, <code>websearch</code> ou <code>composite</code>. Le magasin vectoriel gère la recherche traditionnelle de documents basée sur le RAG, la recherche sur le web récupère les informations les plus récentes qui ne sont pas stockées dans le magasin vectoriel, et l&#x27;approche composite combine les deux lorsque des informations provenant de sources multiples sont nécessaires.</p><p>Si les documents sont jugés appropriés, l&#x27;agent de synthèse génère une réponse claire et adaptée au contexte. Toutefois, si les documents sont insuffisants ou non pertinents, l&#x27;agent de réécriture des requêtes reformule la requête pour améliorer la recherche. Cette requête révisée réinitialise alors le processus de routage, ce qui permet au système d&#x27;affiner sa recherche et d&#x27;améliorer le résultat final.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ed8701a7f15558fbf2e967a884b3e770eccb826b-1256x1092.png" style="width: 100%;" /><h3>Produits requis</h3><p>Ce flux de travail s&#x27;appuie sur les composants de base suivants pour exécuter l&#x27;exemple de manière efficace :</p><ul><li>Python 3.10</li><li><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">Bloc-notes Jupyter</a></li><li>Azure OpenAI</li><li>Elasticsearch</li><li>LangChain</li></ul><p>Avant de poursuivre, vous serez invité à configurer l&#x27;ensemble des variables d&#x27;environnement requises pour cet exemple.</p><pre><code>AZURE_OPENAI_ENDPOINT="Add your azure openai endpoint"
AZURE_OPENAI_KEY="Add your azure openai key"
AZURE_OPENAI_DEPLOYMENT="gpt-4.1"
AZURE_OPENAI_API_VERSION="Add your azure openai api version"

ES_ENDPOINT = "Add your Elasticsearch ENDPOINT"
ES_API_KEY = "Add your Elasticsearch API KEY"</code></pre><h3>Sources de données</h3><p>Ce processus est illustré à l&#x27;aide d&#x27;un sous-ensemble du jeu de données AG News. L&#x27;ensemble des données comprend des articles d&#x27;actualité dans diverses catégories, telles que International, Sports, Affaires et Science/Technologie.</p><pre><code>dataset = load_dataset("ag_news", split="train[:1000]")
docs = [
    Document(
        page_content=sample["text"],
        metadata={"category": sample["label"]}
    )
    for sample in dataset
]</code></pre><p>Le <a href="https://python.langchain.com/docs/integrations/vectorstores/elasticsearch/">module ElasticsearchStore</a> est utilisé à partir de <code>langchain_elasticsearch</code> comme magasin de vecteurs. Pour la recherche, nous mettons en œuvre la stratégie SparseVectorStrategy, en utilisant <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>, le modèle d&#x27;intégration propriétaire d&#x27;Elastic. Il est essentiel de confirmer que le modèle ELSER est correctement installé et déployé dans votre environnement Elasticsearch avant d&#x27;initier le magasin de vecteurs.</p><pre><code>elastic_vectorstore = ElasticsearchStore.from_documents(
    docs,
    es_url=ES_ENDPOINT,
    es_api_key=ES_API_KEY,
    index_name=index_name,
    strategy=SparseVectorStrategy(model_id=".elser_model_2"),
)

elastic_vectorstore.client.indices.refresh(index=index_name)</code></pre><p>La fonctionnalité de recherche sur le web est mise en œuvre à l&#x27;aide de <a href="https://python.langchain.com/api_reference/community/tools/langchain_community.tools.ddg_search.tool.DuckDuckGoSearchRun.html">DuckDuckGoSearchRun</a> des outils de la communauté LangChain, ce qui permet au système de récupérer efficacement des informations en direct sur le web. Vous pouvez également envisager d&#x27;utiliser d&#x27;autres API de recherche qui peuvent fournir des résultats plus pertinents. Cet outil a été choisi car il permet d&#x27;effectuer des recherches sans avoir besoin d&#x27;une clé API.</p><pre><code>duckduckgo = DuckDuckGoSearchRun(description= "A custom DuckDuckGo search tool for finding latest news stories.", verbose=True)
def websearch_retriever(query):
    results = duckduckgo.run(f"{query}")
    return results</code></pre><p>L&#x27;extracteur composite est conçu pour les requêtes qui nécessitent une combinaison de sources. Il est utilisé pour fournir une réponse complète et contextuelle précise en récupérant simultanément des données en temps réel sur le web et en consultant les informations historiques du magasin de vecteurs.</p><pre><code>def composite_retriever(query):
    related_docs = vectorstore_retriever(query)
    related_docs += websearch_retriever(query)
    return related_docs</code></pre><h3>Mise en place des agents</h3><p>Dans l&#x27;étape suivante, les agents LLM sont définis pour fournir des capacités de raisonnement et de prise de décision au sein de ce flux de travail. Les chaînes LLM que nous créerons sont les suivantes <code>router_chain</code>, <code>grade_docs_chain</code>, <code>rewrite_query_chain</code>, et <code>summary_chain</code>.</p><p>L&#x27;agent routeur utilise un assistant LLM pour déterminer la source de données la plus appropriée pour une requête donnée au moment de l&#x27;exécution. L&#x27;agent de classement évalue la pertinence des documents récupérés. Si les documents sont jugés pertinents, ils sont transmis à l&#x27;agent de synthèse pour générer un résumé. Dans le cas contraire, l&#x27;agent de réécriture reformule la requête et la renvoie au processus de routage pour une nouvelle tentative de recherche. Vous trouverez les instructions pour tous les agents dans la section chaînes LLM du <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">carnet de notes</a>.</p><pre><code>class RouteQuery(BaseModel):
    datasource: Literal["vectorstore", "websearch", "composite"] = Field(
        ...,
        description="Choose to route the query to web search, vectorstore or composite."
    )

router_prompt = ChatPromptTemplate.from_template("""You are an assistant that decides the best data source for questions based on news articles.
Choose one of the following options:
- 'vectorstore': for general, background, or historical news articles.
- 'websearch': for recent discoveries, 'latest', 'current', or '2025' type queries.
- 'composite': when the question needs both historical and current knowledge on news articles.

Question: {query}

Return one word: 'vectorstore', 'websearch', or 'composite'.
""")
router_structured = llm.with_structured_output(RouteQuery)
router_chain: RunnableSequence = router_prompt | router_structured</code></pre><p>Le site <code>llm.with_structured_output</code> contraint les résultats du modèle à suivre un schéma prédéfini par le BaseModel sous la classe <code>RouteQuery</code>, ce qui garantit la cohérence des résultats. La deuxième ligne compose un <code>RunnableSequence</code> en reliant <code>router_prompt</code> à <code>router_structured</code>, formant un pipeline dans lequel l&#x27;invite d&#x27;entrée est traitée par le modèle de langage pour produire des résultats structurés et conformes au schéma.</p><h3>Définir les nœuds d&#x27;un graphique</h3><p>Cette partie consiste à définir les états du graphe, qui représentent les données circulant entre les différents composants du système. Une spécification claire de ces états garantit que chaque nœud du flux de travail sait à quelles informations il peut accéder et les mettre à jour.</p><pre><code>class RAGState(TypedDict):
    query: str
    docs: List[Document]
    router: str
    summary: str
    self_reflection: bool
    retry_count: int = 0</code></pre><p>Une fois les états définis, l&#x27;étape suivante consiste à définir les nœuds du graphe. Les nœuds sont en quelque sorte les unités fonctionnelles du graphique qui effectuent des opérations spécifiques sur les données. Il y a 7 nœuds différents dans notre pipeline.</p><pre><code>def router(state: RAGState):
   router = router_chain.invoke({'query': state["query"]})
   logger.info(f"Router selected the datasource: {router.datasource}")
   logger.info(f"User query: {state['query']}")
   return {"router": router.datasource}

def vectorstore(state: RAGState):
   return {"docs": vectorstore_retriever(state["query"])}

def websearch(state: RAGState):
   return {"docs": websearch_retriever(state["query"])}

def composite(state: RAGState):
   return {"docs": composite_retriever(state["query"])}

def self_reflection(state: RAGState):
   evaluation = grade_docs_chain.invoke(
       {"query": state["query"], "docs": state["docs"]}
   )
   if evaluation.binary_score:
       logger.info(f"Self-reflection passed -- binary_score={evaluation.binary_score}")
   else:
       logger.info(f"Self-reflection failed -- binary_score={evaluation.binary_score}")

   return {
       "self_reflection": evaluation.binary_score,
   }

def query_rewriter(state: RAGState):
   retry_count = state.get("retry_count", 0) + 1
   new_query = rewrite_query_chain.invoke({"query": state["query"]})
   logger.info(f"Query rewritten: {new_query}, retry_count: {retry_count}")
   return {
       "query": new_query,
       "retry_count": retry_count,
   }

def summarize(state: RAGState):
   summary = summarize_chain.run(
       query=state["query"],
       docs=state["docs"],
   )
   return {"summary": summary}</code></pre><p>Le nœud <code>query_rewriter</code> a deux fonctions dans le flux de travail. Tout d&#x27;abord, il réécrit la requête de l&#x27;utilisateur à l&#x27;aide du site <code>rewrite_query_chain</code> pour améliorer la recherche lorsque les documents évalués par l&#x27;agent d&#x27;autoréflexion sont jugés insuffisants ou non pertinents. Deuxièmement, il sert de compteur pour savoir combien de fois la requête a été réécrite.</p><p>Chaque fois que le nœud est invoqué, il incrémente le site <code>retry_count</code> stocké dans l&#x27;état du flux de travail. Ce mécanisme empêche le flux de travail d&#x27;entrer dans une boucle infinie. Si le site <code>retry_count</code> dépasse un seuil prédéfini, le système peut passer à un état d&#x27;erreur, à une réponse par défaut ou à toute autre condition prédéfinie de votre choix.</p><h3>Compilation du graphique</h3><p>La dernière étape consiste à définir les arêtes du graphe et à ajouter toutes les conditions nécessaires avant de le compiler. Chaque graphe doit partir d&#x27;un nœud de départ désigné, qui sert de point d&#x27;entrée au flux de travail. Les arêtes du graphique représentent le flux de données entre les nœuds et peuvent être de deux types :</p><ul><li>Arêtes droites : Ils définissent un flux direct et inconditionnel d&#x27;un nœud à l&#x27;autre. Chaque fois que le premier nœud termine sa tâche, le flux de travail passe automatiquement au nœud suivant le long de la ligne droite.</li><li>Arêtes conditionnelles : Elles permettent au flux de travail de se ramifier en fonction de l&#x27;état actuel ou des résultats du calcul d&#x27;un nœud. Le nœud suivant est sélectionné dynamiquement en fonction de conditions telles que les résultats de l&#x27;évaluation, les décisions de routage ou le nombre de tentatives.</li></ul><pre><code>graph.add_edge(START, "router")

def after_router(state: RAGState):
   route = state.get("router", None)
   if route == "vectorstore":
       return "vectorstore"
   elif route == "websearch":
       return "websearch"
   else:
       return "composite"

def after_self_reflection(state: RAGState):
   if state["self_reflection"]:
           return "summarize"
   return "query_rewriter"

def after_query_rewriter(state: RAGState):
   while state['retry_count'] <= 3:
           return "router"
   raise RuntimeError("Maximum retries (3) reached -- evaluation failed.")

graph.add_conditional_edges(
   "router",
   after_router,
   {
       "vectorstore": "vectorstore",
       "websearch": "websearch",
       "composite": "composite"
   }
)

graph.add_edge("vectorstore", "self_reflection")
graph.add_edge("websearch", "self_reflection")
graph.add_edge("composite", "self_reflection")
graph.add_conditional_edges(
   "self_reflection",
   after_self_reflection,
   {
       "summarize": "summarize",
       "query_rewriter": "query_rewriter"
   }
)
graph.add_conditional_edges("query_rewriter", after_query_rewriter, {"router": "router"})
graph.add_edge("summarize", END)
agent=graph.compile()</code></pre><p>Votre premier pipeline RAG agentique est donc prêt et peut être testé à l&#x27;aide de l&#x27;agent compilé.</p><pre><code>result = agent.invoke({"query": query1})
logger.info(f"\nFinal Summary:\n: {result['summary']}")</code></pre><h3>Test du pipeline RAG agentique</h3><p>Nous allons maintenant tester ce pipeline en utilisant trois types de requêtes distinctes, comme indiqué ci-dessous. Il convient de noter que les résultats peuvent varier et que les exemples présentés ci-dessous n&#x27;illustrent qu&#x27;un résultat potentiel.</p><pre><code>query1="What are the latest AI models released this month?"
query2="What technological innovations are discussed in Sci/Tech news?"
query3="Compare a Sci/Tech article from the dataset with a current web article about AI trends."</code></pre><p>Pour la première requête, le routeur sélectionne <code>websearch</code> comme source de données. La requête échoue à l&#x27;évaluation de l&#x27;autoréflexion et est ensuite redirigée vers l&#x27;étape de réécriture de la requête, comme le montre la sortie.</p><pre><code>INFO     | __main__:router:11 - Router selected the datasource: websearch
INFO     | __main__:router:12 - User query: What are the latest AI models released this month?
Latest Singapore news, including the city state's relationships with Malaysia and Mahathir, China and Xi Jinping, and the rest of Southeast Asia. 3 days ago · The latest military news, insights and analysis from China. All the latest news, opinions and analysis on Hong Kong, China, Asia and around the world Latest news, in-depth features and opinion on Malaysia, covering politics, economy, society and the Asean member-nation's relationships with China, Singapore, and other Southeast Asian ... Oct 12, 2025 · Brics (an acronym for Brazil, Russia, India, China and South Africa) refers to an association of 10 leading emerging markets. The other member states are Egypt, Ethiopia, ...
INFO     | __main__:self_reflection:31 - Self-reflection failed -- binary_score=False
INFO     | __main__:query_rewriter:40 - Query rewritten: query='Which AI models have been officially released in June 2024?', retry_count: 1
INFO     | __main__:router:11 - Router selected the datasource: websearch
INFO     | __main__:router:12 - User query: query='Which AI models have been officially released in June 2024?'
Dream Machine is a text-to-video model created by Luma Labs and launched in June 2024 . It generates video output based on user prompts or still images. Dream Machine has been noted for its ability to realistically capture motion... Released in June 2023. In June 2024 , Baidu announced Ernie 4.0 Turbo. In April 2025, Ernie 4.5 Turbo and X1 Turbo were released . These models are optimized for faster response times and lower operational costs.[28][29]. The meaning of QUERY is question, inquiry. How to use query in a sentence. Synonym Discussion of Query. QUERY definition: 1. a question, often expressing doubt about something or looking for an answer from an authority.... Learn more. Query definition: a question; an inquiry.. See examples of QUERY used in a sentence.
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:<module>:2 - 
Final Summary:
: In June 2024, two AI models were officially released: Dream Machine, a text-to-video model launched by Luma Labs, and Ernie 4.0 Turbo, announced by Baidu, which is optimized for faster response times and lower operational costs.</code></pre><p>Ensuite, nous examinons un exemple où <code>vectorstore</code> est utilisé, avec la deuxième requête.</p><pre><code>INFO     | __main__:router:11 - Router selected the datasource: vectorstore
INFO     | __main__:router:12 - User query: What technological innovations are discussed in Sci/Tech news?
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:<module>:2 - 
Final Summary:
: Recent Sci/Tech news highlights several technological innovations: NASA is collaborating with Silicon Valley firms to build a powerful Linux-based supercomputer to support theoretical research and shuttle engineering; new chromatin transfer techniques have enabled the cloning of cats; cybersecurity advancements are being discussed in relation to protecting personal technology; Princeton University scientists assert that existing technologies can be used immediately to stabilize global warming; and a set of GameBoy micro-games has been recognized for innovation in game design.</code></pre><p>La requête finale est dirigée vers la recherche composite, qui utilise à la fois le magasin de vecteurs et la recherche sur le web.</p><pre><code>INFO     | __main__:router:11 - Router selected the datasource: composite
INFO     | __main__:router:12 - User query: Compare a Sci/Tech article from the dataset with a current web article about AI trends.
Atlas currently only available on macOS, built on Chromium with planned features like ad-blocking still in development. OpenAI's Atlas browser launched with bold promises of AI -powered web browsing, but early real-world testing reveals a different story. Career-long data are updated to end-of-2024 and single recent year data pertain to citations received during calendar year 2024. The selection is based on the top 100,000 scientists by c-score (with and without self-citations) or a percentile rank of 2% or above in the sub-field. In this article I list 45 AI tools across 21 different categories. After exploring all the available options in each category, I've carefully selected the best tools based on my personal experience. Reading a complex technical article ? Simply highlight confusing terminology and ask "what's this?" to receive instant explanations. compare browsers. Comparison showing traditional browser navigation versus OpenAI Atlas AI -powered workflows. After putting Gemini, ChatGPT, Grok, and DeepSeek through rigorous testing in October 2025, it's clear that there isn't one AI that reigns supreme across all categories.
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:<module>:2 - 
Final Summary:
: A Sci/Tech article from the dataset highlights NASA's development of robust artificial intelligence software for planetary rovers, aiming to make them more self-reliant and capable of decision-making during missions. In contrast, a current web article about AI trends focuses on the proliferation of AI-powered tools across various categories, including browsers like OpenAI Atlas, and compares leading models such as Gemini, ChatGPT, Grok, and DeepSeek, noting that no single AI currently excels in all areas. While the NASA article emphasizes specialized AI applications for autonomous robotics in space exploration, the current trends article showcases the broadening impact of AI across consumer and professional technologies, with ongoing competition and rapid innovation among major AI platforms.</code></pre><p>Dans le flux de travail ci-dessus, le RAG agentique détermine intelligemment quelle source de données utiliser lors de la recherche d&#x27;informations pour une requête de l&#x27;utilisateur, améliorant ainsi la précision et la pertinence de la réponse. Vous pouvez créer des exemples supplémentaires pour tester l&#x27;agent et examiner les résultats pour voir s&#x27;ils produisent des résultats intéressants.</p><h2>Meilleures pratiques pour l&#x27;élaboration de flux de travail agentiques de RAG</h2><p>Maintenant que nous comprenons le fonctionnement du RAG agentique, examinons quelques bonnes pratiques pour la mise en place de ces flux de travail. Le respect de ces lignes directrices contribuera à maintenir l&#x27;efficacité du système et à en faciliter l&#x27;entretien.</p><ul><li><strong>Préparez-vous à des solutions de repli</strong>: Planifiez à l&#x27;avance des stratégies de repli pour les scénarios dans lesquels une étape du flux de travail échoue. Il peut s&#x27;agir de renvoyer des réponses par défaut, de déclencher des états d&#x27;erreur ou d&#x27;utiliser d&#x27;autres outils. Cela permet au système de gérer les défaillances de manière gracieuse sans interrompre le flux de travail global.</li><li><strong>Mettre en œuvre une journalisation complète</strong>: Essayez de mettre en œuvre la journalisation à chaque étape du flux de travail, comme les tentatives, les résultats générés, les choix de routage et les réécritures de requêtes. Ces journaux permettent d&#x27;améliorer la transparence, de faciliter le débogage et d&#x27;affiner les messages-guides, le comportement de l&#x27;agent et les stratégies de recherche au fil du temps.</li><li><strong>Sélectionner le modèle de flux de travail approprié</strong>: Examinez votre cas d&#x27;utilisation et sélectionnez le modèle de flux de travail qui répond le mieux à vos besoins. Utilisez des flux séquentiels pour le raisonnement étape par étape, des flux parallèles pour les sources de données indépendantes et des modèles d&#x27;orchestrateur-worker pour les requêtes multi-outils ou complexes.</li><li><strong>Incorporer des stratégies d&#x27;évaluation</strong>: Intégrer des mécanismes d&#x27;évaluation à différents stades du processus. Il peut s&#x27;agir d&#x27;agents d&#x27;autoréflexion, de classement des documents extraits ou de contrôles de qualité automatisés. L&#x27;évaluation permet de vérifier que les documents récupérés sont pertinents, que les réponses sont exactes et que toutes les parties d&#x27;une requête complexe sont traitées.</li></ul><h2>Défis</h2><p>Si les systèmes agentiques RAG offrent des avantages significatifs en termes d&#x27;adaptabilité, de précision et de raisonnement dynamique, ils s&#x27;accompagnent également de certains défis qui doivent être relevés lors de leur conception et de leur mise en œuvre. Voici quelques-uns des principaux défis à relever :</p><ul><li><strong>Flux de travail complexes</strong>: Au fur et à mesure de l&#x27;ajout d&#x27;agents et de points de décision, le flux de travail global devient de plus en plus complexe. Cela peut augmenter les risques d&#x27;erreurs ou de défaillances au moment de l&#x27;exécution. Dans la mesure du possible, donnez la priorité à la rationalisation des flux de travail en éliminant les agents redondants et les points de décision inutiles.</li><li><strong>Évolutivité</strong>: Il peut être difficile de faire évoluer les systèmes RAG agentiques pour traiter de grands ensembles de données et des volumes d&#x27;interrogation élevés. Incorporer des stratégies efficaces d&#x27;indexation, de mise en cache et de traitement distribué pour maintenir les performances à l&#x27;échelle.</li><li><strong>Orchestration et surcharge de calcul</strong>: L&#x27;exécution de flux de travail avec plusieurs agents nécessite une orchestration avancée. Cela implique une programmation minutieuse, la gestion des dépendances et la coordination des agents afin d&#x27;éviter les goulets d&#x27;étranglement et les conflits, autant d&#x27;éléments qui ajoutent à la complexité globale du système.</li><li><strong>Complexité de l&#x27;évaluation</strong>: L&#x27;évaluation de ces flux de travail présente des défis inhérents, car chaque étape nécessite une stratégie d&#x27;évaluation distincte. Par exemple, l&#x27;étape RAG doit être évaluée en fonction de la pertinence et de l&#x27;exhaustivité des documents récupérés, tandis que les résumés générés doivent être vérifiés en termes de qualité et d&#x27;exactitude. De même, l&#x27;efficacité de la reformulation de la requête nécessite une logique d&#x27;évaluation distincte pour déterminer si la requête réécrite améliore les résultats de la recherche.</li></ul><h2>Conclusion</h2><p>Dans cet article de blog, nous avons présenté le concept de RAG agentique et souligné comment il améliore le cadre traditionnel de RAG en incorporant des capacités autonomes de l&#x27;IA agentique. Nous avons exploré les caractéristiques principales du RAG agentique et les avons démontrées à l&#x27;aide d&#x27;un exemple pratique, en construisant un assistant de nouvelles utilisant Elasticsearch comme magasin de vecteurs et LangChain pour créer le cadre agentique.</p><p>En outre, nous avons discuté des meilleures pratiques et des principaux défis à prendre en compte lors de la conception et de la mise en œuvre d&#x27;un pipeline RAG agentique. Ces idées sont destinées à guider les développeurs dans la création de systèmes agentiques robustes, évolutifs et efficaces qui combinent de manière effective la recherche, le raisonnement et la prise de décision.</p><h2>Prochaines étapes</h2><p>Le flux de travail que nous avons mis en place est simple et laisse une large place aux améliorations et à l&#x27;expérimentation. Nous pouvons l&#x27;améliorer en expérimentant divers modèles d&#x27;intégration et en affinant les stratégies de recherche. En outre, l&#x27;intégration d&#x27;un agent de reclassement pour hiérarchiser les documents récupérés pourrait être bénéfique. Un autre domaine d&#x27;exploration concerne le développement de stratégies d&#x27;évaluation pour les cadres agentiques, en particulier l&#x27;identification d&#x27;approches communes et réutilisables applicables à différents types de cadres. Enfin, l&#x27;expérimentation de ces cadres sur des ensembles de données plus vastes et plus complexes.</p><p>En attendant, si vous avez des expériences similaires à partager, nous serions ravis de les connaître ! N&#x27;hésitez pas à nous faire part de vos commentaires ou à vous connecter avec nous via notre <a href="https://ela.st/slack">canal Slack communautaire</a> ou nos <a href="https://discuss.elastic.co/c/security">forums de discussion</a>.</p><h2>Ressources</h2><ul><li><a href="https://arxiv.org/abs/2310.11511">Self-RAG : Apprendre à retrouver, générer et critiquer par l&#x27;autoréflexion</a></li><li><a href="https://arxiv.org/abs/2501.09136">Génération assistée par récupération agentique : Une enquête sur la recherche agentique et la génération augmentée : une enquête sur la recherche agentique et la génération augmentée</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[La recherche hybride sans prise de tête : simplifier la recherche hybride avec des extracteurs]]></title>
            <description><![CDATA[Découvrez comment simplifier la recherche hybride dans Elasticsearch avec un format de requête à champs multiples pour les extracteurs linéaires et RRF, et créez des requêtes sans aucune connaissance préalable de votre index Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Mike Pellegrini]]></dc:creator>
            <pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4494f98ae3958bbdbc6171df9677fc4d65ec5640-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La <a href="https://www.elastic.co/what-is/hybrid-search">recherche hybride</a> est largement reconnue comme une approche de recherche puissante, combinant la précision et la vitesse de la <a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch#lexical-search---sparse-retrieval">recherche lexicale</a> avec les capacités de langage naturel de la <a href="https://www.elastic.co/what-is/semantic-search">recherche sémantique</a>. Cependant, son application pratique peut s&#x27;avérer délicate, nécessitant souvent une connaissance approfondie de votre index et la construction de requêtes verbeuses avec des configurations non triviales. Dans ce blog, nous allons voir comment le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">format de requête multi-champs pour les extracteurs linéaires et RRF</a> rend la recherche hybride plus simple et plus accessible, en éliminant les maux de tête courants et en vous permettant de tirer parti de toute sa puissance avec plus de facilité. Nous verrons également comment le format d&#x27;interrogation à champs multiples vous permet d&#x27;effectuer des recherches hybrides sans aucune connaissance préalable de votre index.</p><h2>Le problème de l&#x27;étendue des scores</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c8b49153c47d2cdc233c0d2e440db04711d48ca5-1600x1600.jpg" style="width: 100%;" /><p>Pour préparer le terrain, examinons l&#x27;une des principales raisons pour lesquelles la recherche hybride peut s&#x27;avérer difficile : la variation des fourchettes de scores. Notre vieil ami <a href="https://www.elastic.co/elasticon/conf/2016/sf/improved-text-scoring-with-bm25">BM25</a> produit des scores non bornés. En d&#x27;autres termes, BM25 peut générer des scores allant de près de 0 à (théoriquement) l&#x27;infini. En revanche, les requêtes portant sur les champs <code>dense_vector</code> produiront des scores limités entre 0 et 1. Pour aggraver ce problème, <code>semantic_text</code> obscurcit le type de champ utilisé pour indexer les embeddings, de sorte qu&#x27;à moins d&#x27;avoir une connaissance détaillée de la configuration de votre index et de votre point de terminaison d&#x27;inférence, il peut être difficile de savoir quelle sera la plage de scores de votre requête. Cela pose un problème lorsqu&#x27;on essaie d&#x27;intercaler des résultats de recherche lexicaux et sémantiques, car les résultats lexicaux peuvent prendre le pas sur les résultats sémantiques, même si ces derniers sont plus pertinents. La solution généralement acceptée pour ce problème est de normaliser les scores avant d&#x27;entrelacer les résultats. Elasticsearch dispose de deux outils pour cela, les extracteurs <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever">linéaires</a> et <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rrf-retriever">RRF</a>.<br/></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/264481268c8b6ac259e3c257b85431b513f16672-1077x586.png" style="width: 100%;" /><p>Le récupérateur <strong>RRF</strong> applique l&#x27;<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">algorithme RRF</a>, en utilisant le rang du document comme mesure de la pertinence et en écartant le score. Étant donné que le score n&#x27;est pas pris en compte, les écarts de score ne posent pas de problème.</p><p>L&#x27;extracteur <strong>linéaire</strong> utilise une combinaison linéaire pour déterminer le score final d&#x27;un document. Il s&#x27;agit de prendre le score de chaque composante de la requête pour le document, de le normaliser et de l&#x27;additionner pour obtenir le score total. Mathématiquement, l&#x27;opération peut être exprimée comme suit :</p><pre><code>Total Score = 𝚺(N(Sx))</code></pre><p>Où <code>N</code> est la fonction de normalisation et SX est le score de la requête X. La fonction de normalisation est essentielle ici, car elle transforme le score de chaque requête pour utiliser le même intervalle. Pour en savoir plus sur le retriever linéaire <a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">, cliquez ici.</a></p><h2>La décomposition</h2><p>Les utilisateurs peuvent mettre en œuvre une recherche hybride efficace à l&#x27;aide de ces outils, mais cela nécessite une certaine connaissance de votre index. Prenons un exemple avec l&#x27;extracteur linéaire, où nous allons interroger un index avec deux champs :</p><pre><code>PUT linear_retriever_example
{
  "mappings": {
    "properties": {
      "semantic_text_field": { <1>
        "type": "semantic_text",
        "inference_id": ".multilingual-e5-small-elasticsearch"
      },
      "text_field": { <2>
        "type": "text"
      }
    }
  }
}</code></pre><p>1. <code>semantic_text_field</code> est un champ <code>semantic_text</code> qui utilise <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">E5</a>, un modèle d&#x27;intégration de texte.</p><p>2. <code>text_field</code> est un champ standard <code>text</code></p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "match": { <1>
                  "semantic_text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}</code></pre><p>1. Nous utilisons une requête <code>match</code> sur notre champ <code>semantic_text</code>, dont <a href="https://www.elastic.co/search-labs/blog/semantic-search-match-knn-sparse-vector#we-made-match-happen-in-semantic-search!">la prise en charge a été ajoutée dans Elasticsearch 8.18/9.0.</a></p><p><br/>Lors de la construction de la requête, nous devons garder à l&#x27;esprit que <code>semantic_text_field</code> utilise un modèle d&#x27;intégration de texte, de sorte que toute requête sur ce site générera un score entre 0 et 1. Nous devons également savoir que <code>text_field</code> est un champ standard de <code>text</code> et que les requêtes sur ce champ génèreront donc un score non borné. Pour créer un ensemble de résultats pertinents, nous devons utiliser un extracteur qui normalisera les résultats des requêtes avant de les combiner. Dans cet exemple, nous utilisons l&#x27;extracteur linéaire avec la normalisation <code>minmax</code>, qui normalise le score de chaque requête à une valeur comprise entre 0 et 1.</p><p>La construction de la requête dans cet exemple est assez simple car seuls deux champs sont concernés. Toutefois, la situation peut se compliquer très rapidement à mesure que l&#x27;on ajoute d&#x27;autres champs, de types différents. Cela démontre que la rédaction d&#x27;une requête de recherche hybride efficace nécessite souvent une connaissance plus approfondie de l&#x27;index interrogé, afin que les scores des composantes de la requête soient correctement normalisés avant d&#x27;être combinés. Cela constitue un obstacle à l&#x27;adoption plus large de la recherche hybride.</p><h3>Regroupement de requêtes</h3><p>Étendons l&#x27;exemple : Et si nous voulions interroger un champ <code>text</code> et deux champs <code>semantic_text</code>? Nous pourrions construire une requête comme celle-ci :</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_1",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_2",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}</code></pre><p>Cela semble être une bonne chose à première vue, mais il y a un problème potentiel. Désormais, les matchs sur le terrain <code>semantic_text</code> représentent ⅔ du score total :</p><pre><code>Total Score = N(semantic_text_field_1 score) + N(semantic_text_field_2 score) + N(text_field score)</code></pre><p>Ce n&#x27;est probablement pas ce que vous souhaitez, car cela crée un score déséquilibré. Les effets ne sont peut-être pas très visibles dans un exemple comme celui-ci, qui ne comporte que trois champs, mais ils deviennent problématiques lorsqu&#x27;un plus grand nombre de champs sont interrogés. Par exemple, la plupart des index contiennent beaucoup plus de champs lexicaux que de champs sémantiques (c.-à-d. <code>dense_vector</code>, <code>sparse_vector</code>, ou <code>semantic_text</code>). Que se passerait-il si nous interrogions un index comportant 9 champs lexicaux et 1 champ sémantique en utilisant le modèle ci-dessus ? Les correspondances lexicales représenteraient 90% du score, ce qui réduirait l&#x27;efficacité de la recherche sémantique.</p><p>Une solution courante consiste à regrouper les requêtes en catégories lexicales et sémantiques et à pondérer les deux de manière égale. Cela permet d&#x27;éviter que l&#x27;une ou l&#x27;autre catégorie ne domine le score total.</p><p>Mettons cela en pratique. À quoi ressemblerait cette approche de requêtes groupées pour cet exemple en utilisant l&#x27;outil de recherche linéaire ?</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "linear": {
              "retrievers": [
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_1",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                },
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_2",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                }
              ]
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}</code></pre><p>Wow, ça devient verbeux ! Vous avez peut-être même dû faire défiler l&#x27;écran de haut en bas plusieurs fois pour examiner l&#x27;ensemble de la requête ! Ici, nous utilisons deux niveaux de normalisation pour créer les groupes de requêtes. Mathématiquement, elle peut être exprimée comme suit :</p><pre><code>Total Score = N(N(semantic_text_field_1 score) + N(semantic_text_field_2 score)) + N(text_field score)</code></pre><p>Ce deuxième niveau de normalisation garantit que les requêtes portant sur les champs <code>semantic_text</code> et <code>text</code> sont pondérées de manière égale. Notez que nous omettons la normalisation de second niveau pour <code>text_field</code> dans cet exemple puisqu&#x27;il n&#x27;y a qu&#x27;un seul champ lexical, ce qui vous évite <em>encore plus</em> de verbosité.</p><p>Cette structure d&#x27;interrogation est déjà lourde, et nous n&#x27;interrogeons que trois champs. Il devient de plus en plus difficile à gérer, même pour les praticiens chevronnés de la recherche, au fur et à mesure que l&#x27;on interroge davantage de champs.</p><h2>Le format d&#x27;interrogation à champs multiples</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dd04e1686076a574b717c1460acfe4eb79299208-1600x1600.jpg" style="width: 100%;" /><p>Nous avons ajouté le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">format de requête multi-champs</a> pour les extracteurs linéaires et RRF dans Elasticsearch 8.19, 9.1 et <a href="https://www.elastic.co/cloud/serverless">serverless</a> pour simplifier tout cela. Vous pouvez maintenant effectuer la même requête que ci-dessus avec just :</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>Ce qui réduit la requête de 55 lignes à seulement 9 ! Elasticsearch utilise automatiquement les mappages d&#x27;index pour :</p><ul><li>Déterminer le type de chaque champ interrogé</li><li>Regrouper chaque champ dans une catégorie lexicale ou sémantique</li><li>Pondérer chaque catégorie de manière égale dans la note finale</li></ul><p>Cela permet à n&#x27;importe qui d&#x27;exécuter une requête de recherche hybride efficace sans avoir besoin de connaître les détails de l&#x27;index ou les points de terminaison d&#x27;inférence utilisés.</p><p>Lorsque vous utilisez la méthode RRF, vous pouvez omettre le site <code>normalizer</code>, car le rang est utilisé comme indicateur de la pertinence :</p><pre><code>GET rrf_retriever_example/_search
{
  "retriever": {
    "rrf": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo"
    }
  }
}</code></pre><h2>Renforcement par champ</h2><p>Lors de l&#x27;utilisation de l&#x27;extracteur linéaire, vous pouvez appliquer un boost par champ pour ajuster l&#x27;importance des correspondances dans certains champs. Par exemple, disons que vous interrogez quatre champs : deux champs <code>semantic_text</code> et deux champs <code>text</code>:</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field_1", "text_field_2" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>Par défaut, chaque champ est pondéré de manière égale dans son groupe (lexical ou sémantique). La répartition des points est la suivante :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ba31c76189e3a1e5b1638437ccf0528aafec2598-1600x549.png" style="width: 100%;" /><p>En d&#x27;autres termes, chaque champ représente 25% du score total.</p><p>Nous pouvons utiliser la syntaxe <code>field^boost</code> pour ajouter un boost par champ à n&#x27;importe quel champ. Appliquons un boost de 2 à <code>semantic_text_field_1</code> et <code>text_field_1</code>:</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1^2", "semantic_text_field_2", "text_field_1^2", "text_field_2" ]
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>La répartition des points est maintenant la suivante :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/de20e555d52f914bf483a048d056f54f4fece757-1600x549.png" style="width: 100%;" /><p>Chaque groupe de requêtes est toujours pondéré de manière égale, mais la pondération des champs à l&#x27;intérieur des groupes a changé :</p><ul><li><code>semantic_text_field_1</code> est 66% du score du groupe de requêtes sémantiques, 33% du score total</li><li><code>text_field_1</code> est 66% du score du groupe de requêtes lexicales, 33% du score total</li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ℹ️ Notez que la fourchette de score total ne changera pas lorsqu'une majoration par champ est appliquée. Il s'agit d'un effet secondaire voulu de la normalisation des scores, qui garantit que les scores des requêtes lexicales et sémantiques restent directement comparables entre eux.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ℹ️ Le boosting par champ peut également être utilisé avec le récupérateur RRF dans Elasticsearch 9.2+.</td>
                    </tr>
              </tbody>
            </table>
          <h3>Résolution sur les caractères génériques</h3><p>Vous pouvez utiliser le caractère générique <code>*</code> dans le paramètre <code>fields</code> pour faire correspondre plusieurs champs. Si l&#x27;on reprend l&#x27;exemple ci-dessus, cette requête est fonctionnellement équivalente à l&#x27;interrogation explicite des sites<code>emantic_text_field_1</code>, <code>semantic_text_field_2</code> et <code>text_field_1</code>:</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_*", "*_field_1" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>Il est intéressant de noter que le modèle <code>*_field_1</code> correspond à la fois à <code>text_field_1</code> et à <code>semantic_text_field_1</code>. La requête sera exécutée comme si chacun des champs avait été explicitement interrogé. Le fait que le site <code>semantic_text_field_1</code> corresponde aux deux modèles ne pose pas de problème ; tous les noms de champ correspondant sont dédupliqués avant l&#x27;exécution de la requête.</p><p>Vous pouvez utiliser les caractères génériques de différentes manières :</p><ul><li>Correspondance des préfixes (ex : <code>*_text_field</code>)</li><li>Correspondance en ligne (ex : <code>semantic_*_field</code>)</li><li>Correspondance des suffixes (ex : <code>semantic_text_field_*</code>)</li></ul><p>Vous pouvez également utiliser plusieurs caractères génériques pour appliquer une combinaison des éléments ci-dessus, par exemple <code>*_text_field_*</code>.</p><h3>Champs de requête par défaut</h3><p>Le format d&#x27;interrogation à champs multiples vous permet également d&#x27;interroger un index dont vous ignorez tout. Si vous omettez le paramètre <code>fields</code>, il interrogera tous les champs spécifiés par le <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules">paramètre d&#x27;indexation index.query.default_field</a>:</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>Par défaut, <code>index.query.default_field</code> est défini comme <code>*</code>. Ce caractère générique permet de résoudre tous les types de champs de l&#x27;index qui prennent en charge les requêtes de termes, ce qui est le cas de la plupart d&#x27;entre eux. Les exceptions sont les suivantes :</p><ul><li><code>dense_vector</code> champs</li><li><code>rank_vector</code> champs</li><li>Champs de géométrie : <code>geo_point</code>, <code>shape</code></li></ul><p>Cette fonctionnalité est particulièrement utile lorsque vous souhaitez effectuer une recherche hybride sur un index fourni par un tiers. Le format d&#x27;interrogation à champs multiples vous permet d&#x27;exécuter une requête appropriée de manière simple. Il suffit d&#x27;exclure le paramètre <code>fields</code> pour que tous les champs applicables soient interrogés.</p><h2>Conclusion</h2><p>Le problème de la plage de scores peut faire de la recherche hybride efficace un casse-tête à mettre en œuvre, en particulier lorsque l&#x27;on ne dispose que de peu d&#x27;informations sur l&#x27;index interrogé ou sur les points de terminaison d&#x27;inférence utilisés. Le format d&#x27;interrogation à champs multiples pour les extracteurs linéaires et RRF atténue cette difficulté en intégrant une approche de recherche hybride automatisée, basée sur le regroupement de requêtes, dans une API simple et facile d&#x27;accès. Des fonctionnalités supplémentaires, telles que le renforcement par champ, la résolution des caractères génériques et les champs de requête par défaut, permettent d&#x27;étendre les fonctionnalités à de nombreux cas d&#x27;utilisation.</p><h2>Essayez le format d&#x27;interrogation à champs multiples dès aujourd&#x27;hui</h2><p>Vous pouvez tester les extracteurs linéaires et RRF avec le format de requête multi-champs dans des projets Elasticsearch <a href="https://www.elastic.co/cloud/serverless">Serverless</a> entièrement gérés avec un <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/create-serverless-project">essai gratuit</a>. Il est également disponible en version stack à partir de 8.19 &amp; 9.1.</p><p>Démarrez en quelques minutes sur votre environnement local à l&#x27;aide d&#x27;une simple commande :</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un agent d'IA pour les RH avec Elastic Agent Builder et GPT-OSS]]></title>
            <description><![CDATA[Découvrez comment créer un agent d'intelligence artificielle capable de répondre à des requêtes en langage naturel sur les données RH de vos employés en utilisant Elastic Agent Builder et GPT-OSS.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
            <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/05d2d0513fff67d975f9223d75108aa9f50646bc-1600x914.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Introduction</h2><p>Cet article vous montrera comment construire un agent d&#x27;IA pour les RH en utilisant <a href="https://openai.com/index/introducing-gpt-oss/">GPT-OSS</a> et Elastic Agent Builder. L&#x27;agent peut répondre à vos questions sans envoyer de données à OpenAI, Anthropic ou tout autre service externe.</p><p>Nous allons utiliser LM Studio pour servir GPT-OSS localement et le connecter à Elastic Agent Builder.</p><p>À la fin de cet article, vous disposerez d&#x27;un agent d&#x27;IA personnalisé capable de répondre à des questions en langage naturel sur les données de vos employés, tout en conservant un contrôle total sur vos informations et votre modèle.</p><h2>Produits requis</h2><p>Pour cet article, vous avez besoin de :</p><ul><li><a href="https://www.elastic.co/cloud">Elastic Cloud</a> hébergé 9.2, déploiement sans serveur ou <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">local</a>.</li><li>Machine avec 32 Go de RAM recommandée (minimum 16 Go pour GPT-OSS 20B)</li><li><a href="https://lmstudio.ai/">LM Studio</a> installé</li><li><a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a> installé</li></ul><h2>Pourquoi utiliser GPT-OSS ?</h2><p>Avec un LLM local, vous avez la possibilité de le déployer dans votre propre infrastructure et de l&#x27;adapter à vos besoins. Tout cela en gardant le contrôle sur les données que vous partagez avec le modèle et, bien sûr, sans avoir à payer de licence à un fournisseur externe.</p><p>OpenAI <a href="https://openai.com/index/introducing-gpt-oss/">a publié GPT-OSS</a> le 5 août 2025, dans le cadre de son engagement envers l&#x27;écosystème des modèles ouverts.</p><p>Le modèle de paramètres 20B offre :</p><ul><li><strong>Capacités d&#x27;utilisation des outils</strong></li><li><strong>Inférence efficace</strong></li><li><strong>Compatible avec le SDK OpenAI</strong></li><li><strong>Compatible avec les flux de travail agentiques</strong></li></ul><p>Comparaison des points de repère :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/29160e3345352088e8213297630882f252b00c47-1600x680.png" style="width: 100%;" /><h2>Architecture de la solution</h2><p>L&#x27;architecture fonctionne entièrement sur votre machine locale. Elastic (exécuté dans Docker) communique directement avec votre LLM local via LM Studio, et Elastic Agent Builder utilise cette connexion pour créer des agents d&#x27;IA personnalisés qui peuvent interroger les données de vos employés.</p><p>Pour plus de détails, consultez cette <a href="https://www.elastic.co/docs/solutions/observability/connect-to-own-local-llm">documentation</a>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4a886750ff25fa8bb7aefc7448161e52cf73ed3-1600x896.png" style="width: 100%;" /><h2>Construire un agent d&#x27;IA pour les RH : étapes</h2><p>Nous diviserons la mise en œuvre en 5 étapes :</p><ol><li>Configurer LM studio avec un modèle local</li><li>Déployer Elastic local avec Docker</li><li>Créer le connecteur OpenAI dans Elastic</li><li>Téléchargement des données des employés vers Elasticsearch</li><li>Créez et testez votre agent d&#x27;intelligence artificielle</li></ol><h2>Étape 1 : Configurer LM Studio avec GPT-OSS 20B</h2><p>LM Studio est une application conviviale qui vous permet d&#x27;exécuter localement de grands modèles linguistiques sur votre ordinateur. Il fournit un serveur d&#x27;API compatible avec OpenAI, ce qui facilite l&#x27;intégration avec des outils tels qu&#x27;Elastic sans processus de configuration complexe. Pour plus de détails, reportez-vous à la <a href="https://lmstudio.ai/docs/app">documentation de LM Studio</a>.</p><p>Tout d&#x27;abord, téléchargez et installez <span class="unknown__pt__mark__51566e628851">LM Studio</span> depuis le site officiel. Une fois installée, ouvrez l&#x27;application.</p><h3>Dans l&#x27;interface de LM Studio :</h3><ol><li>Allez dans l&#x27;onglet recherche et cherchez &quot;GPT-OSS&quot;</li><li>Sélectionnez le site <code>openai/gpt-oss-20b</code> à partir d&#x27;OpenAI</li><li>Cliquez sur télécharger</li></ol><p>La taille de ce modèle devrait être d&#x27;environ <strong>12,10 Go.</strong> Le téléchargement peut prendre quelques minutes, en fonction de votre connexion internet.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5d01bc4dcb377b5259fc6b521fe2425a31b90ca4-1312x872.png" style="width: 100%;" /><h4>Une fois le modèle téléchargé :</h4><ol><li>Aller dans l&#x27;onglet du serveur local</li><li>Sélectionner l&#x27;openai/gpt-oss-20b</li><li>Utiliser le port par défaut 1234</li><li>Dans le panneau de droite, cliquez sur <strong>Charger </strong>et réglez la longueur du contexte sur <strong>40K</strong> ou plus.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e546033f916381647b876815b2c1f1ae2a08365f-326x337.png" style="width: 100%;" /><p>5. Cliquez sur démarrer le serveur</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/28ee78a3caa84d14e04db3d42f30acbe4d4d005a-1312x872.png" style="width: 100%;" /><p>Vous devriez voir ceci si le serveur est en cours d&#x27;exécution.</p><pre><code>[LM STUDIO SERVER] Success! HTTP server listening on port 1234
[LM STUDIO SERVER] Supported endpoints:
[LM STUDIO SERVER] ->	GET  http://localhost:1234/v1/models
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/responses
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/chat/completions
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/completions
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/embeddings
Server started.</code></pre><h2>Étape 2 : Déployer Elastic local avec Docker</h2><p>Nous allons maintenant configurer Elasticsearch et Kibana localement à l&#x27;aide de Docker. Elastic fournit un script pratique qui gère l&#x27;ensemble du processus d&#x27;installation. Pour plus de détails, voir la <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">documentation officielle</a>.</p><h3>Exécuter le script start-local</h3><p>Exécutez la commande suivante dans votre terminal :</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>Ce script va :</p><ul><li>Télécharger et configurer Elasticsearch et Kibana</li><li>Démarrer les deux services à l&#x27;aide de Docker Compose</li><li>Activation automatique d&#x27;une licence d&#x27;essai Platinum de 30 jours</li></ul><h3>Résultats attendus</h3><p>Attendez le message suivant et enregistrez le mot de passe et la clé API indiqués ; vous en aurez besoin pour accéder à Kibana :</p><pre><code>🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: KSUlOMNr
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: cnJGX0pwb0JhOG00cmNJVklUNXg6cnNJdXZWMnM4bncwMllpQlFlUTlWdw==
Learn more at https://github.com/elastic/start-local</code></pre><h3>Accéder à Kibana</h3><p>Ouvrez votre navigateur et naviguez vers :</p><pre><code>http://localhost:5601</code></pre><p>Connectez-vous en utilisant les informations d&#x27;identification obtenues dans la sortie du terminal.</p><h3>Activer le constructeur d&#x27;agents</h3><p>Une fois connecté à Kibana, naviguez vers <strong>Management </strong>&gt;<strong> AI </strong>&gt;<strong> Agent Builder </strong>et activez l&#x27;Agent Builder.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/92e104cb846c20d875865ded8a3d37f5c7daae9b-1491x1528.png" style="width: 100%;" /><h2>Étape 3 : Créer le connecteur OpenAI dans Elastic</h2><p>Nous allons maintenant configurer Elastic pour qu&#x27;il utilise votre LLM local.</p><h3>Connecteurs d&#x27;accès</h3><ol><li>Dans Kibana</li><li>Allez dans <strong>Paramètres du projet</strong> &gt; <strong>Gestion</strong></li><li>Sous <strong>Alertes et aperçus</strong>, sélectionnez <strong>Connecteurs</strong></li><li>Cliquez sur Créer un connecteur</li></ol><h3>Configurer le connecteur</h3><p>Sélectionnez <strong>OpenAI</strong> dans la liste des connecteurs. LM Studio utilise le SDK OpenAI, ce qui le rend compatible.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5ac87042e086c7a2bd47a8039e646ec831f0dcc6-923x974.png" style="width: 100%;" /><p>Remplissez les champs avec ces valeurs :</p><ul><li><strong>Nom du connecteur : </strong>LM Studio - GPT-OSS 20B</li><li><strong>Sélectionnez un fournisseur OpenAI : </strong>Autre (Service compatible avec l&#x27;OpenAI)</li><li><strong>URL : </strong><code>http://host.docker.internal:1234/v1/chat/completions</code></li><li><strong>Modèle par défaut : </strong>openai/gpt-oss-20b</li><li><strong>Clé API :</strong> testkey-123 (n&#x27;importe quel texte fonctionne, car le serveur LM Studio ne nécessite pas d&#x27;authentification).</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2084ac32fcf1fb810c8b54ecab1c85a1e3e8905b-672x1302.png" style="width: 100%;" /><p>Pour terminer la configuration, cliquez sur <strong>Save &amp; test.</strong></p><p><strong>Important :</strong> Activez l&#x27;option &quot;<strong>Enable native function calling</strong>&quot; (activer l&#x27;appel de fonctions natives) ; cette option est nécessaire pour que l&#x27;Agent Builder fonctionne correctement. Si vous ne l&#x27;activez pas, vous obtiendrez une erreur <strong><code>No tool calls found in the response</code></strong>.</p><h3>Tester la connexion</h3><p>Elastic devrait automatiquement tester la connexion. Si tout est configuré correctement, vous obtiendrez un message de réussite comme celui-ci :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f567d767f1969c4730c1daa92f651789dc3742ac-1042x812.png" style="width: 100%;" /><p>Réponse :</p><pre><code>{
  "status": "ok",
  "data": {
    "id": "chatcmpl-flj9h0hy4wcx4bfson00an",
    "object": "chat.completion",
    "created": 1761189456,
    "model": "openai/gpt-oss-20b",
    "choices": [
      {
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "Hello! 👋 How can I assist you today?",
          "reasoning": "Just greet.",
          "tool_calls": []
        },
        "logprobs": null,
        "finish_reason": "stop"
      }
    ],
    "usage": {
      "prompt_tokens": 69,
      "completion_tokens": 23,
      "total_tokens": 92
    },
    "stats": {},
    "system_fingerprint": "openai/gpt-oss-20b"
  },
  "actionId": "ee1c3aaf-bad0-4ada-8149-118f52dad757"
}</code></pre><h2>Étape 4 : Téléchargement des données des employés vers Elasticsearch</h2><p>Nous allons maintenant télécharger l&#x27;<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">ensemble de données relatives aux employés des RH</a> afin de montrer comment l&#x27;agent travaille avec des données sensibles. J&#x27;ai généré un ensemble de données fictives avec cette structure.</p><h3>Structure de l&#x27;ensemble de données</h3><pre><code>{
  "employee_id": "0f4dce68-2a09-4cb1-b2af-6bcb4821539b",
  "full_name": "Daffi Stiebler",
  "email": "lscutchings0@huffingtonpost.com",
  "date_of_birth": "1975-06-20T15:39:36Z",
  "hire_date": "2025-07-28T00:10:45Z",
  "job_title": "Physical Therapy Assistant",
  "department": "HR",
  "salary": "108455",
  "performance_rating": "Needs Improvement",
  "years_of_experience": 2,
  "skills": "Java",
  "education_level": "Master's Degree",
  "manager": "Carl MacGibbon",
  "emergency_contact": "Leigha Scutchings",
  "home_address": "5571 6th Park"
}</code></pre><h3>Créer l&#x27;index avec les correspondances</h3><p>Tout d&#x27;abord, créez l&#x27;index avec les correspondances appropriées. Notez que nous utilisons des champs <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a> pour certains champs clés ; cela permet des capacités de recherche sémantique pour notre index.</p><pre><code>​​PUT hr-employees
{
  "mappings": {
    "properties": {
      "@timestamp": {
        "type": "date"
      },
      "employee_id": {
        "type": "keyword"
      },
      "full_name": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "email": {
        "type": "keyword"
      },
      "date_of_birth": {
        "type": "date",
        "format": "iso8601"
      },
      "hire_date": {
        "type": "date",
        "format": "iso8601"
      },
      "job_title": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "department": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "salary": {
        "type": "double"
      },
      "performance_rating": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "years_of_experience": {
        "type": "long"
      },
      "skills": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "education_level": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "manager": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "emergency_contact": {
        "type": "keyword"
      },
      "home_address": {
        "type": "keyword"
      },
      "employee_semantic": {
        "type": "semantic_text"
      }
    }
  }
}</code></pre><h3>Index avec Bulk API</h3><p>Copiez et collez le <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">jeu de données</a> dans votre Dev Tools dans Kibana et exécutez-le :</p><pre><code>POST hr-employees/_bulk
{"index": {}}
{"employee_id": "57728b91-e5d7-4fa8-954a-2384040d3886", "full_name": "Filide Gane", "email": "vhallahan1@booking.com", "job_title": "Business Systems Development Analyst", "department": "Marketing", "salary": "$52330.27", "performance_rating": "Meets Expectations", "years_of_experience": 12, "skills": "Java", "education_level": "Bachelor's Degree", "date_of_birth": "2000-02-07T16:49:32Z", "hire_date": "2023-11-07T13:03:16Z", "manager": "Freedman Kings", "emergency_contact": "Vilhelmina Hallahan", "home_address": "75 Dennis Junction"}
{"index": {}}
{"employee_id": "...", ...}</code></pre><h3>Vérifier les données</h3><p>Exécutez une requête pour vérifier :</p><pre><code>GET hr-employees/_search</code></pre><h2>Étape 5 : Créer et tester votre agent d&#x27;intelligence artificielle</h2><p>Une fois tout configuré, il est temps de créer un agent d&#x27;IA personnalisé à l&#x27;aide d&#x27;Elastic Agent Builder. Pour plus de détails, voir la <a href="https://www.elastic.co/docs/solutions/search/agent-builder/get-started">documentation Elastic</a>.</p><h3>Ajouter le connecteur</h3><p>Avant de pouvoir créer notre nouvel agent, nous devons configurer notre Agent builder pour qu&#x27;il utilise notre connecteur personnalisé appelé <code>LM Studio - GPT-OSS 20B</code>, car le connecteur par défaut est <a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">Elastic Managed LLM</a>. Pour cela, nous devons aller dans <strong>Project Setting</strong> &gt; <strong>Management</strong> &gt; <strong>GenAI Settings</strong>; nous sélectionnons alors celui que nous avons créé et cliquons sur <strong>Save.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/11e830c3e2fb4c298b020c928fa5422f3397ba08-1600x1152.png" style="width: 100%;" /><h3>Agent d&#x27;accès Constructeur</h3><ol><li>Aller aux <strong>agents</strong></li><li>Cliquez sur <strong>Créer un nouvel agent</strong></li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1e60541563650163f972ac9088dc1ed1de759a7-1600x1054.png" style="width: 100%;" /><h3>Configurer l&#x27;agent</h3><p>Pour créer un nouvel agent, les champs obligatoires sont l&#x27;<strong>identifiant de l&#x27;agent</strong>, le <strong>nom d&#x27;affichage</strong> et les <strong>instructions d&#x27;affichage.</strong></p><p>Mais il existe d&#x27;autres options de personnalisation, comme les instructions personnalisées qui indiquent comment votre agent va se comporter et interagir avec vos outils, à la manière d&#x27;une invite système, mais pour notre agent personnalisé. Les étiquettes permettent d&#x27;organiser les agents, la couleur de l&#x27;avatar et le symbole de l&#x27;avatar.</p><p>Ceux que j&#x27;ai choisis pour notre agent sur la base de l&#x27;ensemble des données sont les suivants :<br/><br/><strong>Agent ID :</strong> <code>hr_assistant</code></p><p><strong>Instructions personnalisées :</strong></p><pre><code>You are an HR Analytics Assistant that helps answer questions about employee data.
When responding to queries:
- Provide clear, concise answers
- Include relevant employee details (name, department, salary, skills)
- Format monetary values with currency symbols
- Be professional and maintain data confidentiality</code></pre><p><br/>Étiquettes : <code>Human Resources</code> et <code>GPT-OSS</code></p><p>Nom d&#x27;affichage : <code>HR Analytics Assistant</code></p><p>Description de l&#x27;affichage :</p><pre><code>A specialized AI assistant for Human Resources that helps analyze employee data, compensation, performance metrics, and talent management. Ask questions about employees, departments, salaries, or performance analytics.</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f94bb2bf08497e5e756ca76b30a3a51f42927756-1424x1217.png" style="width: 100%;" /><p>Une fois toutes les données saisies, nous pouvons cliquer sur <strong>Enregistrer</strong> notre nouvel agent.</p><h3>Tester l&#x27;agent</h3><p>Vous pouvez désormais poser des questions en langage naturel sur les données de vos employés, et GPT-OSS 20B comprendra l&#x27;intention et générera une réponse appropriée.</p><h4>Prompt :</h4><pre><code>Which employee is the one with the highest salary in the hr-employees index?</code></pre><h4>Réponse :</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/94ad19f80b96304028a59f60beca51dfc9aecc8a-899x631.png" style="width: 100%;" /><p>Le processus de l&#x27;agent a été le suivant :</p><p>1. Comprendre votre question à l&#x27;aide du connecteur GPT-OSS</p><p>2. Générer la requête Elasticsearch appropriée (à l&#x27;aide des outils intégrés ou d&#x27;<a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> personnalisés)</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f2961d0d1b97475f6dda300acee84da540938e6-844x466.png" style="width: 100%;" /><p>3. Récupérer les enregistrements des salariés correspondants</p><p>4. Présenter les résultats en langage naturel avec un formatage approprié</p><p>Contrairement à la recherche lexicale traditionnelle, l&#x27;agent alimenté par GPT-OSS comprend l&#x27;intention et le contexte, ce qui facilite la recherche d&#x27;informations sans connaître les noms exacts des champs ou la syntaxe de la requête. Pour plus de détails sur le processus de réflexion de l&#x27;agent, voir cet <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance">article</a>.</p><h2>Conclusion</h2><p>Dans cet article, nous avons construit un agent d&#x27;IA personnalisé à l&#x27;aide de l&#x27;Agent Builder d&#x27;Elastic pour se connecter au modèle OpenAI GPT-OSS fonctionnant localement. En déployant à la fois Elastic et le LLM sur votre machine locale, cette architecture vous permet de tirer parti des capacités d&#x27;IA générative tout en conservant un contrôle total sur vos données, le tout sans envoyer d&#x27;informations à des services externes.</p><p>Nous avons utilisé GPT-OSS 20B à titre expérimental, mais les modèles officiellement recommandés pour Elastic Agent Builder sont référencés <a href="https://www.elastic.co/docs/solutions/search/agent-builder/models#recommended-models">ici.</a> Si vous avez besoin de capacités de raisonnement plus avancées, il existe également la <a href="https://huggingface.co/openai/gpt-oss-120b">variante de paramètre 120B</a> qui est plus performante pour les scénarios complexes, bien qu&#x27;elle nécessite une machine plus sophistiquée pour fonctionner localement. Pour plus de détails, consultez la <a href="https://openai.com/open-models/">documentation officielle d&#x27;OpenAI</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Principaux projets d'Elastic Agent Builder et enseignements tirés de Cal Hacks 12.0]]></title>
            <description><![CDATA[Explorez les meilleurs projets Elastic Agent Builder de Cal Hacks 12.0 et plongez dans nos commentaires techniques sur Serverless, ES|QL et les architectures d'agents.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/agent-builder-projects-learnings-cal-hacks-12-0</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/agent-builder-projects-learnings-cal-hacks-12-0</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8ba034a6f19b50521f541b8131756a8acdb52975-1280x960.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<a href="https://www.youtube.com/watch/GUI0WXNZduo">Watch video on YouTube</a><p>Il y a quelques semaines, nous avons eu l&#x27;incroyable opportunité de sponsoriser <a href="https://cal-hacks-12-0.devpost.com/">Cal Hacks 12.0</a>, l&#x27;un des plus grands hackathons en personne avec plus de 2000 participants venus du monde entier. Nous avons proposé une piste de prix dédiée à la meilleure utilisation d&#x27;Elastic Agent Builder sur Serverless, et la réponse a été phénoménale. En seulement 36 heures, nous avons reçu 29 soumissions qui utilisaient Agent Builder de manière créative, de la construction d&#x27;outils de renseignement sur les incendies de forêt aux validateurs StackOverflow.</p><p>Au-delà des projets impressionnants, l&#x27;expérience de Cal Hacks 12.0 nous a également apporté quelque chose de tout aussi précieux : un retour d&#x27;information rapide et non filtré de la part de développeurs qui découvrent notre pile pour la première fois. Les hackathons sont des tests de pression uniques, avec des délais serrés, une absence totale de connaissances préalables et des obstacles imprévisibles (comme les fameuses pannes de WiFi). Ils révèlent exactement les points forts de l&#x27;expérience du développeur et ceux sur lesquels il faut encore travailler. Cela est d&#x27;autant plus important aujourd&#x27;hui que les développeurs interagissent avec la pile Elastic de nouvelles façons, de plus en plus par le biais de flux de travail pilotés par LLM. Dans cet article de blog, nous allons approfondir ce que les participants ont construit avec Agent Builder et ce que nous avons appris au cours du processus.</p><h2>Les projets gagnants</h2><h3>Première place : AgentOverflow</h3><p>Stack Overflow reconstruit pour l&#x27;ère du LLM et de l&#x27;agent.</p><p>Pour en savoir plus sur AgentOverflow <a href="https://devpost.com/software/agentoverflow">, cliquez ici.</a></p><p>AgentOverflow s&#x27;attaque à un problème que rencontrent la plupart des développeurs d&#x27;IA : Les LLM hallucinent, les historiques de conversation disparaissent et les développeurs perdent du temps à résoudre les mêmes problèmes.</p><p>AgentOverflow capture, valide et fait réapparaître de véritables paires problème-solution, afin que les développeurs puissent sortir de la spirale de l&#x27;hallucination et livrer plus rapidement.</p><h4>Comment cela fonctionne-t-il ?</h4><p><strong>1. Partager JSON - le &quot;schéma de solution&quot;.</strong></p><p>Un clic à partir d&#x27;un partage de Claude permet de récupérer, d&#x27;extraire et d&#x27;assembler une solution de partage JSON, qui est un format structuré contenant :</p><ul><li>Problème</li><li>Contexte</li><li>Code</li><li>Balises</li><li>Vérification des étapes de la solution.</li></ul><p>Un validateur (LAVA) vérifie et renforce la structure, l&#x27;utilisateur ajoute une ligne de contexte supplémentaire, puis le tout est stocké et indexé dans Elasticsearch.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/45a3e96f4474050a855419628c2a7338bb12c706-1600x877.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2d65cb154d8ee32fc96ff17dfa5b0bf2636e3777-1600x1002.png" style="width: 100%;" /><p><strong>2. Trouver la solution</strong></p><p>Lorsque vous êtes bloqué, cliquez sur <code>Find Solution</code> et AgentOverflow scrapera votre conversation actuelle, l&#x27;utilisera pour construire une requête, et lancera une recherche hybride Elasticsearch pour remonter à la surface :</p><ul><li>Corrections classées et validées par la communauté</li><li>Les invites exactes qui ont résolu le problème à l&#x27;origine</li></ul><p>Cela permet aux développeurs de copier, coller et débloquer rapidement leur session en cours.</p><p><strong>3. MCP - injection de contexte pour les LLM</strong></p><p>En se connectant aux solutions structurées stockées dans Elasticsearch via MCP (Model Context Protocol), les LLM sont alimentés en contexte à haut signal (code, journaux, configurations, corrections antérieures) au moment de l&#x27;exécution sans bruit supplémentaire.</p><p>AgentOverflow utilise Agent Builder avec Elasticsearch comme couche de mémoire structurée qui injecte un contexte pertinent dans les LLM. Ils passent ainsi du statut de chatbots passifs à celui de résolveurs de problèmes conscients du contexte.</p><h3>Deuxième prix : MarketMind</h3><p>Une vue interprétable en temps réel de l&#x27;énergie du marché, alimentée par six agents élastiques.</p><p>Pour en savoir plus sur MarketMind <a href="https://devpost.com/software/marketmind-b6cy2q">, cliquez ici.</a></p><p>MarketMind a gagné sa place en offrant aux traders débutants une plateforme qui convertit les données fragmentées du marché en signaux clairs et en temps réel. Au lieu de jongler avec l&#x27;évolution des prix, les fondamentaux, le sentiment et la volatilité sur différents outils, MarketMind consolide toutes ces informations sur une seule plateforme, aidant ainsi les traders à obtenir des informations exploitables. Ce projet a également utilisé des requêtes ES|QL complexes lors de la création de ses agents.</p><h4>Comment cela fonctionne-t-il ?</h4><p><strong>1. Collecter des données de marché en temps réel</strong></p><p>MarketMind extrait de Yahoo Finance des données sur l&#x27;évolution des cours, les fondamentaux, le sentiment, la volatilité et le risque. Ces données sont ingérées et organisées en plusieurs index Elasticsearch.</p><p><strong>2. Six agents spécialisés analysent le marché</strong></p><p>Chaque agent, créé avec Agent Builder, se concentre sur une couche différente du marché. Ils lisent un index Elasticsearch, calculent leurs propres mesures spécifiques au domaine et génèrent une sortie JSON standardisée avec des scores et un raisonnement.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7d9716cca06a047a2b3584378b5c7e592a785ba1-1284x878.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5aac6a833347c0d2e596c02049ec4b4d3aae5cd7-794x764.png" style="width: 100%;" /><p><strong>3. Agréger les signaux dans un modèle unifié d&#x27;&quot;énergie de marché&quot;.</strong></p><p>Les résultats combinés apparaissent sous forme d&#x27;impulsions lumineuses autour de chaque action, indiquant si la dynamique se renforce, si le risque augmente ou si le sentiment change.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/46b3da8e3d528c5dd4e2829416c5446098acb3aa-744x718.png" style="width: 100%;" /><p><strong>4. Visualiser les informations</strong></p><p>Le frontend a été construit avec React et <a href="https://github.com/vercel/next.js">Next.js</a>, en utilisant TypeScript, des visuels SVG basés sur la physique, et <a href="https://github.com/chartjs">Chart.js</a> pour les graphiques de chandeliers en direct. L&#x27;analyse brute est ainsi transformée en un retour d&#x27;information exploitable en temps réel.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3f000c043117b77ed4127202be5a49c12e3682ba-1600x930.png" style="width: 100%;" /><h2>Autres projets intéressants :</h2><p>Voici d&#x27;autres concurrents de taille qui ont utilisé Elastic dans différentes parties de leur pile :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/76c49a853426844f475cd6b2a74999e60af20e8c-926x1080.png" style="width: 100%;" /><p>Vous trouverez <a href="https://cal-hacks-12-0.devpost.com/submissions/search?utf8=%E2%9C%93&amp;prize_filter%5Bprizes%5D%5B%5D=91882">ici la</a> liste complète des projets qui ont été soumis à notre circuit.</p><h2>Ce que nous ont appris les développeurs</h2><ul><li><strong>Agent Builder est convivial :</strong></li></ul><p>La plupart des équipes n&#x27;avaient jamais utilisé Elastic auparavant et étaient encore en mesure de créer des agents rapidement avec peu de soutien. Nous avons organisé un atelier pour ceux qui avaient besoin de plus de conseils, mais la plupart ont été en mesure d&#x27;ingérer leurs données et de créer un agent pour effectuer des actions sur ces données.</p><ul><li><strong>Les LLM excellent dans les  requêtes<code>kNN</code></strong>, mais ont encore besoin d&#x27;être guidés dans la création d&#x27;ES|QL :</li></ul><p>Demander à ChatGPT-5 de générer des requêtes ES|QL renvoyait des informations incorrectes, mélangeant souvent ES|QL et SQL. Alimenter le LLM avec les documents dans un fichier markdown semblait être une solution viable.</p><ul><li><strong>Les fonctions ES|QL en mode instantané ont fait l&#x27;objet d&#x27;une fuite dans la documentation :</strong></li></ul><p>Les fonctions d&#x27;agrégation <code>FIRST</code> et <code>LAST</code> ont été glissées involontairement dans nos documents ES|QL. Parce que nous avons fourni ces documents à ChatGPT, le modèle a consciencieusement utilisé ces fonctions, même si elles ne sont pas encore disponibles dans Serverless. Grâce au feedback du groupe, l&#x27;ingénierie a rapidement ouvert et fusionné un correctif pour supprimer les fonctions de la documentation publiée<a href="https://github.com/elastic/elasticsearch/pull/137341">(PR #137341</a>).</p><ul><li><strong>Absence d&#x27;orientations spécifiques à Serverless :</strong></li></ul><p>Une équipe a essayé d&#x27;activer <code>LOOKUP JOIN</code> sur un index qui n&#x27;a pas été créé en mode consultation. Le message d&#x27;erreur les a envoyés à la recherche de commandes qui n&#x27;existent pas sur Serverless. Nous avons relayé cette information à l&#x27;équipe produit, qui a immédiatement ouvert un correctif pour un message actionnable spécifique à Serverless. À plus long terme, l&#x27;objectif est de masquer entièrement la complexité de la réindexation<a href="https://github.com/elastic/elasticsearch-serverless/issues/4838">(problème n° 4838</a>).</p><ul><li><strong>Valeur des événements en personne :</strong></li></ul><p>Les hackathons en ligne sont formidables, mais rien n&#x27;égale la boucle de rétroaction rapide que vous obtenez lorsque vous déboguez épaule contre épaule avec des constructeurs. Nous avons vu des équipes intégrer Agent Builder dans différents cas d&#x27;utilisation, repérer où l&#x27;expérience des développeurs avec ES|QL pouvait être améliorée, et résoudre les problèmes beaucoup plus rapidement qu&#x27;en essayant de le faire sur des canaux asynchrones.</p><h2>Conclusion</h2><p>Cal Hacks 12.0 nous a offert plus qu&#x27;un week-end de démonstrations intéressantes ; il nous a également permis de comprendre comment les nouveaux développeurs interagissent avec la pile Elastic. En seulement 36 heures, nous avons vu des équipes prendre en main Agent Builder, ingérer des données dans Elasticsearch, concevoir des systèmes multi-agents et tester nos fonctionnalités de différentes manières. L&#x27;événement nous a également rappelé pourquoi les événements en personne sont importants. Les boucles de rétroaction rapides, les conversations réelles et le débogage pratique nous ont aidés à comprendre les besoins actuels des développeurs. Nous sommes ravis d&#x27;apporter ce que nous avons appris à l&#x27;équipe d&#x27;ingénieurs. Nous vous donnons rendez-vous au prochain hackathon.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Création d'une salle de presse avec agent LLM, protocole A2A et MCP dans Elasticsearch : Partie II]]></title>
            <description><![CDATA[Découvrez comment construire une salle de presse hybride spécialisée pour les agents LLM en utilisant le protocole A2A pour la collaboration entre agents et MCP pour l'accès aux outils dans Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/a2a-protocol-mcp-llm-agent-workflow-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/a2a-protocol-mcp-llm-agent-workflow-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Justin Castilla]]></dc:creator>
            <pubDate>Mon, 24 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f87330e5d4ca961593b3cfb861ca850a4cc34186-1519x1173.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>A2A et MCP : le code en action</h2><p>Cet article est le complément de l&#x27;article &quot;Creating an LLM Agent newsroom with A2A protocol and MCP in Elasticsearch !&quot;, qui expliquait les avantages de la mise en œuvre des architectures A2A et MCP au sein du même agent afin de profiter pleinement des avantages uniques des deux frameworks. Un <a href="https://github.com/justincastilla/elastic-newsroom">référentiel</a> est disponible si vous souhaitez exécuter la démo par vous-même.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7196f004089127f83547b2e5dc3f663205cfcdce-1162x1600.png" style="width: 100%;" /><p>Voyons comment les agents de notre salle de presse collaborent en utilisant à la fois A2A et MCP pour produire un article. Le référentiel d&#x27;accompagnement pour voir les agents en action est disponible <a href="https://github.com/justincastilla/elastic-newsroom">ici.</a></p><h3>Étape 1 : Attribution de l&#x27;histoire</h3><p>Le <strong>chef de l&#x27;information</strong> (agissant en tant que client) attribue un sujet :</p><pre><code>{
  "message_type": "task_request",
  "sender": "news_chief",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "story_renewable_energy_2024",
    "assignment": {
      "topic": "Renewable Energy Adoption in Europe",
      "angle": "Policy changes driving solar and wind expansion",
      "target_length": 1200,
      "deadline": "2025-09-30T18:00:00Z"
    }
  }
}</code></pre><h3>Étape 2 : Le journaliste demande des recherches</h3><p>L&#x27;<strong>agent rapporteur</strong> reconnaît qu&#x27;il a besoin d&#x27;informations générales et délègue à l&#x27;<strong>agent chercheur</strong> par l&#x27;intermédiaire de l&#x27;A2A :</p><pre><code>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "researcher_agent",
  "payload": {
    "task_id": "research_eu_renewable_2024",
    "parent_task_id": "story_renewable_energy_2024",
    "capability": "fact_gathering",
    "parameters": {
      "queries": [
        "EU renewable energy capacity 2024",
        "Solar installations growth Europe",
        "Wind energy policy changes 2024"
      ],
      "depth": "comprehensive"
    }
  }
}</code></pre><h3>Étape 3 : Le rapporteur demande le contexte historique à l&#x27;agent d&#x27;archivage</h3><p>L&#x27;<strong>agent rapporteur</strong> reconnaît que le contexte historique renforcerait l&#x27;histoire. Il délègue à l&#x27;<strong>agent d&#x27;archivage</strong> (alimenté par l&#x27;<a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">agent A2A d&#x27;Elastic</a>), via A2A, le soin d&#x27;effectuer des recherches dans les archives d&#x27;articles de la salle de presse alimentées par Elasticsearch :</p><pre><code>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "archive_agent",
  "payload": {
    "task_id": "archive_search_renewable_2024",
    "parent_task_id": "story_renewable_energy_2024",
    "capability": "search_archive",
    "parameters": {
      "query": "European renewable energy policy changes and adoption trends over past 5 years",
      "focus_areas": ["solar", "wind", "policy", "Germany", "France"],
      "time_range": "2019-2024",
      "result_count": 10
    }
  }
}</code></pre><h3>Étape 4 : L&#x27;agent d&#x27;archivage utilise l&#x27;agent Elastic A2A avec MCP</h3><p>L&#x27;<strong>agent d&#x27;archivage</strong> utilise l&#x27;agent A2A d&#x27;Elastic, qui à son tour utilise MCP pour accéder aux outils Elasticsearch. Ceci démontre l&#x27;architecture hybride où A2A permet la collaboration des agents tandis que MCP fournit l&#x27;accès aux outils :</p><pre><code># Archive Agent using Elastic A2A Agent
async def search_historical_articles(self, query_params):
    # The Archive Agent sends a request to Elastic's A2A Agent
    elastic_response = await self.a2a_client.send_request(
        agent="elastic_agent",
        capability="search_and_analyze",
        parameters={
            "natural_language_query": query_params["query"],
            "index_pattern": "newsroom-articles-*",
            "filters": {
                "topics": query_params["focus_areas"],
                "date_range": query_params["time_range"]
            },
            "analysis_type": "trend_analysis"
        }
    )
    
    # Elastic's A2A Agent internally uses MCP tools:
    # - platform.core.search (to find relevant articles)
    # - platform.core.generate_esql (to analyze trends)
    # - platform.core.index_explorer (to identify relevant indices)
    
    return elastic_response</code></pre><p>L&#x27;<strong>agent d&#x27;archivage</strong> reçoit des données historiques complètes de l&#x27;agent A2A d&#x27;Elastic et les renvoie au rapporteur :</p><pre><code>{
  "message_type": "task_response",
  "sender": "archive_agent",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "archive_search_renewable_2024",
    "status": "completed",
    "archive_data": {
      "historical_articles": [
        {
          "title": "Germany's Energiewende: Five Years of Solar Growth",
          "published": "2022-06-15",
          "key_points": [
            "Germany added 7 GW annually 2020-2022",
            "Policy subsidies drove 60% of growth"
          ],
          "relevance_score": 0.94
        },
        {
          "title": "France Balances Nuclear and Renewables",
          "published": "2023-03-20",
          "key_points": [
            "France increased renewable target to 40% by 2030",
            "Solar capacity doubled 2021-2023"
          ],
          "relevance_score": 0.89
        }
      ],
      "trend_analysis": {
        "coverage_frequency": "EU renewable stories increased 150% since 2019",
        "emerging_themes": ["policy incentives", "grid modernization", "battery storage"],
        "coverage_gaps": ["Small member states", "offshore wind permitting"]
      },
      "total_articles_found": 47,
      "search_confidence": 0.91
    }
  }
}</code></pre><p>Cette étape montre comment l&#x27;agent A2A d&#x27;Elastic s&#x27;intègre dans le flux de travail de la salle de presse. L&#x27;Archive Agent (un agent spécifique à la salle de presse) se coordonne avec l&#x27;A2A Agent d&#x27;Elastic (un spécialiste tiers) pour exploiter les puissantes capacités de recherche et d&#x27;analyse d&#x27;Elasticsearch. L&#x27;agent Elastic utilise MCP en interne pour accéder aux outils Elasticsearch, ce qui montre la séparation nette entre la coordination de l&#x27;agent (A2A) et l&#x27;accès aux outils (MCP).</p><h3>Étape 5 : Le chercheur utilise les serveurs MCP</h3><p>L&#x27;<strong>agent chercheur</strong> accède à plusieurs serveurs MCP pour recueillir des informations :</p><pre><code># Researcher Agent using MCP to access tools
async def gather_facts(self, queries):
    results = []
    
    # Use News API MCP Server
    news_data = await self.mcp_client.invoke_tool(
        server="news_api_mcp",
        tool="search_articles",
        parameters={
            "query": queries[0],
            "date_range": "2024-01-01 to 2024-09-30",
            "sources": ["reuters", "bloomberg", "euractiv"]
        }
    )
    
    # Use Statistics MCP Server
    stats_data = await self.mcp_client.invoke_tool(
        server="eurostat_mcp",
        tool="get_energy_statistics",
        parameters={
            "metrics": ["solar_capacity_mw", "wind_capacity_mw"],
            "countries": ["EU27"],
            "year": 2024
        }
    )
    
    # Use Fact-Checking MCP Server
    verified_facts = await self.mcp_client.invoke_tool(
        server="factcheck_mcp",
        tool="verify_claims",
        parameters={
            "claims": [
                "EU solar capacity increased 40% in 2024",
                "Germany leads in wind installations"
            ]
        }
    )
    
    return self._synthesize_research(news_data, stats_data, verified_facts)</code></pre><h3>Étape 6 : Le chercheur renvoie les données au rapporteur</h3><p>L&#x27;<strong>agent chargé de la recherche</strong> renvoie la recherche complète par l&#x27;intermédiaire de l&#x27;A2A :</p><pre><code>{
  "message_type": "task_response",
  "sender": "researcher_agent",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "research_eu_renewable_2024",
    "status": "completed",
    "research_data": {
      "key_facts": [
        {
          "claim": "EU solar capacity reached 260 GW in 2024",
          "source": "Eurostat Q3 2024 Report",
          "confidence": 0.95,
          "verified": true
        },
        {
          "claim": "Germany installed 12 GW of wind capacity in 2024",
          "source": "German Federal Network Agency",
          "confidence": 0.92,
          "verified": true
        }
      ],
      "statistics": {
        "solar_growth_rate": "35%",
        "wind_growth_rate": "28%"
      },
      "sources_count": 15
    }
  }
}</code></pre><h3>Étape 7 : Le journaliste rédige un article</h3><p>L&#x27;<strong>agent rapporteur</strong> utilise les données de recherche et ses propres capacités LLM pour rédiger l&#x27;article. Pendant la rédaction, le Reporter utilise les serveurs MCP pour le style et les modèles :</p><pre><code># Reporter Agent writing with MCP assistance
async def write_article(self, research_data, assignment):
    # Get style guidelines via MCP
    style_guide = await self.mcp_client.get_resource(
        server="newsroom_mcp",
        resource="style://editorial/ap_style_guide"
    )
    
    # Get article template via MCP
    template = await self.mcp_client.get_resource(
        server="newsroom_mcp",
        resource="template://articles/news_story"
    )
    
    # Generate article using LLM + research + style
    draft = await self.llm.generate(
        prompt=f"""
        Write a news article following these guidelines:
        {style_guide}
        
        Using this template:
        {template}
        
        Based on this research:
        {research_data}
        
        Assignment: {assignment}
        """
    )
    
    # Self-evaluate confidence in claims
    confidence_check = await self._evaluate_confidence(draft)
    
    return draft, confidence_check</code></pre><h3>Étape 8 : le manque de confiance déclenche une nouvelle recherche</h3><p>L&#x27;<strong>agent déclarant</strong> évalue son projet et constate qu&#x27;une créance a un faible degré de confiance. Il envoie une autre demande à l&#x27;<strong>agent du chercheur :</strong></p><pre><code>{
  "message_type": "collaboration_request",
  "sender": "reporter_agent",
  "receiver": "researcher_agent",
  "payload": {
    "request_type": "fact_verification",
    "claims": [
      {
        "text": "France's nuclear phase-down contributed to 15% increase in renewable capacity",
        "context": "Discussing policy drivers for renewable growth",
        "current_confidence": 0.45,
        "required_confidence": 0.80
      }
    ],
    "urgency": "high"
  }
}</code></pre><p>Le <strong>chercheur</strong> vérifie l&#x27;allégation à l&#x27;aide des serveurs MCP de vérification des faits et renvoie les informations mises à jour :</p><pre><code>{
  "message_type": "collaboration_response",
  "sender": "researcher_agent",
  "receiver": "reporter_agent",
  "payload": {
    "verified_claims": [
      {
        "original_claim": "France's nuclear phase-down contributed to 15% increase...",
        "verified_claim": "France's renewable capacity increased 18% in 2024, partially offsetting reduced nuclear output",
        "confidence": 0.88,
        "corrections": "Percentage was 18%, not 15%; nuclear phase-down is gradual, not primary driver",
        "sources": ["RTE France", "French Energy Ministry Report 2024"]
      }
    ]
  }
}</code></pre><h3>Étape 9 : Le journaliste révise le texte et le soumet au rédacteur en chef</h3><p>Le <strong>rapporteur</strong> incorpore les faits vérifiés et envoie le projet complet à l&#x27;<strong>agent rédacteur</strong> par l&#x27;intermédiaire de l&#x27;A2A :</p><pre><code>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "editor_agent",
  "payload": {
    "task_id": "edit_renewable_story",
    "parent_task_id": "story_renewable_energy_2024",
    "content": {
      "headline": "Europe's Renewable Revolution: Solar and Wind Surge 30% in 2024",
      "body": "[Full article text...]",
      "word_count": 1185,
      "sources": [/* array of sources */]
    },
    "editing_requirements": {
      "check_style": true,
      "check_facts": true,
      "check_seo": true
    }
  }
}</code></pre><h3>Étape 10 : Examens des éditeurs à l&#x27;aide des outils MCP</h3><p>L&#x27;<strong>agent rédacteur</strong> utilise plusieurs serveurs MCP pour réviser l&#x27;article :</p><pre><code># Editor Agent using MCP for quality checks
async def review_article(self, content):
    # Grammar and style check
    grammar_issues = await self.mcp_client.invoke_tool(
        server="grammarly_mcp",
        tool="check_document",
        parameters={"text": content["body"]}
    )
    
    # SEO optimization check
    seo_analysis = await self.mcp_client.invoke_tool(
        server="seo_mcp",
        tool="analyze_content",
        parameters={
            "headline": content["headline"],
            "body": content["body"],
            "target_keywords": ["renewable energy", "Europe", "solar", "wind"]
        }
    )
    
    # Plagiarism check
    originality = await self.mcp_client.invoke_tool(
        server="plagiarism_mcp",
        tool="check_originality",
        parameters={"text": content["body"]}
    )
    
    # Generate editorial feedback
    feedback = await self._generate_feedback(
        grammar_issues, 
        seo_analysis, 
        originality
    )
    
    return feedback</code></pre><p>Le <strong>rédacteur en chef</strong> approuve l&#x27;article et le transmet :</p><pre><code>{
  "message_type": "task_response",
  "sender": "editor_agent",
  "receiver": "reporter_agent",
  "payload": {
    "status": "approved",
    "quality_score": 9.2,
    "minor_edits": [
      "Changed 'surge' to 'increased' in paragraph 3 for AP style consistency",
      "Added Oxford comma in list of countries"
    ],
    "approved_content": "[Final edited article]"
  }
}</code></pre><h3>Étape 11 : L&#x27;éditeur publie via CI/CD</h3><p>Enfin, l&#x27;<strong>agent imprimeur</strong> publie l&#x27;article approuvé en utilisant les serveurs MCP pour le CMS et le pipeline CI/CD :</p><pre><code># Publisher Agent publishing via MCP
async def publish_article(self, content, metadata):
    # Upload to CMS via MCP
    cms_result = await self.mcp_client.invoke_tool(
        server="wordpress_mcp",
        tool="create_post",
        parameters={
            "title": content["headline"],
            "body": content["body"],
            "status": "draft",
            "categories": metadata["categories"],
            "tags": metadata["tags"],
            "featured_image_url": metadata["image_url"]
        }
    )
    
    post_id = cms_result["post_id"]
    
    # Trigger CI/CD deployment via MCP
    deploy_result = await self.mcp_client.invoke_tool(
        server="cicd_mcp",
        tool="trigger_deployment",
        parameters={
            "pipeline": "publish_article",
            "environment": "production",
            "post_id": post_id,
            "schedule": "immediate"
        }
    )
    
    # Track analytics
    await self.mcp_client.invoke_tool(
        server="analytics_mcp",
        tool="register_publication",
        parameters={
            "post_id": post_id,
            "publish_time": datetime.now().isoformat(),
            "story_id": metadata["story_id"]
        }
    )
    
    return {
        "status": "published",
        "post_id": post_id,
        "url": f"https://newsroom.example.com/articles/{post_id}",
        "deployment_id": deploy_result["deployment_id"]
    }</code></pre><p>L&#x27;<strong>éditeur</strong> confirme la publication via A2A :</p><pre><code>{
  "message_type": "task_complete",
  "sender": "printer_agent",
  "receiver": "news_chief",
  "payload": {
    "task_id": "story_renewable_energy_2024",
    "status": "published",
    "publication": {
      "url": "https://newsroom.example.com/articles/renewable-europe-2024",
      "published_at": "2025-09-30T17:45:00Z",
      "post_id": "12345"
    },
    "workflow_metrics": {
      "total_time_minutes": 45,
      "agents_involved": ["reporter", "researcher", "archive", "editor", "printer"],
      "iterations": 2,
      "mcp_calls": 12
    }
  }
}</code></pre><p>Voici la séquence complète du flux de travail A2A dans le référentiel d&#x27;accompagnement en utilisant les mêmes agents que ceux décrits ci-dessus.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">#</td><td style="border: 1px solid #ccc; padding: 4px;">De</td><td style="border: 1px solid #ccc; padding: 4px;">Pour</td><td style="border: 1px solid #ccc; padding: 4px;">Action</td><td style="border: 1px solid #ccc; padding: 4px;">Protocole</td><td style="border: 1px solid #ccc; padding: 4px;">Description</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">1</td><td style="border: 1px solid #ccc; padding: 4px;">Utilisateur</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Attribuer l'histoire</td><td style="border: 1px solid #ccc; padding: 4px;">HTTP POST</td><td style="border: 1px solid #ccc; padding: 4px;">L'utilisateur soumet le sujet et l'angle de l'article</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">2</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Interne</td><td style="border: 1px solid #ccc; padding: 4px;">Créer une histoire</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Création d'un enregistrement d'histoire avec un identifiant unique</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Affectation des délégués</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Envoi de l'article via le protocole A2A</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">4</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Interne</td><td style="border: 1px solid #ccc; padding: 4px;">Accepter l'affectation</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Stockage interne de l'affectation</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">5</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Serveur MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Générer un plan</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Création d'un plan d'article et de questions de recherche</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">6a</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Chercheur</td><td style="border: 1px solid #ccc; padding: 4px;">Demande de recherche</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Envoie des questions (parallèle avec 6b)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">6b</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Archiviste</td><td style="border: 1px solid #ccc; padding: 4px;">Recherche dans les archives</td><td style="border: 1px solid #ccc; padding: 4px;">A2A JSONRPC</td><td style="border: 1px solid #ccc; padding: 4px;">Recherche d'articles historiques (parallèle avec 6a)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">7</td><td style="border: 1px solid #ccc; padding: 4px;">Chercheur</td><td style="border: 1px solid #ccc; padding: 4px;">Serveur MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Questions de recherche</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Utilise l'Anthropique via le MCP pour répondre aux questions</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">8</td><td style="border: 1px solid #ccc; padding: 4px;">Chercheur</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Recherche en matière de retour</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Réponses à la recherche sur les retours</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">9</td><td style="border: 1px solid #ccc; padding: 4px;">Archiviste</td><td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">Index de recherche</td><td style="border: 1px solid #ccc; padding: 4px;">API REST ES</td><td style="border: 1px solid #ccc; padding: 4px;">Requêtes news_archive index</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10</td><td style="border: 1px solid #ccc; padding: 4px;">Archiviste</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Retour à l'archive</td><td style="border: 1px solid #ccc; padding: 4px;">A2A JSONRPC</td><td style="border: 1px solid #ccc; padding: 4px;">Renvoie les résultats de la recherche historique</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">11</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Serveur MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Générer un article</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Création d'un article dans un contexte de recherche ou d'archives</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">12</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Interne</td><td style="border: 1px solid #ccc; padding: 4px;">Projet de magasin</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Sauvegarde interne du projet</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">13</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Soumettre le projet</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Soumission d'un projet achevé</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">14</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Interne</td><td style="border: 1px solid #ccc; padding: 4px;">Mise à jour de l'histoire</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Stocke le projet, met à jour le statut à "draft_submitted"</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">15</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Éditeur</td><td style="border: 1px solid #ccc; padding: 4px;">Projet de révision</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Routes automatiques vers l'éditeur pour révision</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">16</td><td style="border: 1px solid #ccc; padding: 4px;">Éditeur</td><td style="border: 1px solid #ccc; padding: 4px;">Serveur MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Article de synthèse</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Analyse du contenu à l'aide d'Anthropic via MCP</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">17</td><td style="border: 1px solid #ccc; padding: 4px;">Éditeur</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Retourner à l'examen</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Envoi d'un retour d'information et de suggestions éditoriales</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">18</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Interne</td><td style="border: 1px solid #ccc; padding: 4px;">Revue des magasins</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Stocke les commentaires des éditeurs</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">19</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Appliquer les modifications</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Acheminement du retour d'information sur l'examen au rapporteur</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">20</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Serveur MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Appliquer les modifications</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Révision de l'article en fonction du retour d'information</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">21</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Interne</td><td style="border: 1px solid #ccc; padding: 4px;">Projet de mise à jour</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Mise à jour du projet avec des révisions</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">Reporter</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Retour Révisé</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Retourne l'article révisé</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">23</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Interne</td><td style="border: 1px solid #ccc; padding: 4px;">Mise à jour de l'histoire</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Stocker le projet révisé, statut à "révisé"</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">24</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Éditeur</td><td style="border: 1px solid #ccc; padding: 4px;">Publier un article</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Routes automatiques vers l'éditeur</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">25</td><td style="border: 1px solid #ccc; padding: 4px;">Éditeur</td><td style="border: 1px solid #ccc; padding: 4px;">Serveur MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Générer des étiquettes</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Création d'étiquettes et de catégories</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">26</td><td style="border: 1px solid #ccc; padding: 4px;">Éditeur</td><td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">Index Article</td><td style="border: 1px solid #ccc; padding: 4px;">API REST ES</td><td style="border: 1px solid #ccc; padding: 4px;">Indexe l'article dans l'index news_archive</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">27</td><td style="border: 1px solid #ccc; padding: 4px;">Éditeur</td><td style="border: 1px solid #ccc; padding: 4px;">Système de fichiers</td><td style="border: 1px solid #ccc; padding: 4px;">Sauvegarder la démarque</td><td style="border: 1px solid #ccc; padding: 4px;">Fichier E/S</td><td style="border: 1px solid #ccc; padding: 4px;">Enregistre l'article au format .md fichier dans /articles</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">28</td><td style="border: 1px solid #ccc; padding: 4px;">Éditeur</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Confirmer la publication</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Renvoie l'état de réussite</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">29</td><td style="border: 1px solid #ccc; padding: 4px;">Chef de l'information</td><td style="border: 1px solid #ccc; padding: 4px;">Interne</td><td style="border: 1px solid #ccc; padding: 4px;">Mise à jour de l'histoire</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Mise à jour du statut de l'article à "publié"</td>
                    </tr>
              </tbody>
            </table>
          <h2>Conclusion</h2><p>L&#x27;A2A et le MCP ont tous deux un rôle important à jouer dans le paradigme moderne de l&#x27;infrastructure augmentée-LLM. L&#x27;A2A offre une certaine souplesse pour les systèmes multi-agents complexes, mais potentiellement moins de portabilité et une plus grande complexité opérationnelle. MCP offre une approche standardisée pour l&#x27;intégration des outils, plus simple à mettre en œuvre et à maintenir, bien qu&#x27;il ne soit pas conçu pour gérer l&#x27;orchestration multi-agents.</p><p>Le choix n&#x27;est pas binaire. Comme le montre notre exemple de salle de presse, les systèmes les plus sophistiqués et les plus efficaces soutenus par le LLM combinent souvent les deux approches : les agents se coordonnent et se spécialisent par le biais de protocoles A2A tout en accédant à leurs outils et à leurs ressources par le biais de serveurs MCP. Cette architecture hybride offre les avantages organisationnels des systèmes multi-agents ainsi que les avantages de la normalisation et de l&#x27;écosystème du MCP. Cela suggère qu&#x27;il n&#x27;est peut-être pas nécessaire de faire un choix : il suffit d&#x27;utiliser les deux en tant qu&#x27;approche standard.</p><p>C&#x27;est à vous, en tant que développeur ou architecte, de tester et de déterminer le meilleur mélange de ces deux solutions pour obtenir le bon résultat pour votre cas d&#x27;utilisation spécifique. Comprendre les points forts, les limites et les applications appropriées de chaque approche vous permettra de construire des systèmes d&#x27;IA plus efficaces, plus faciles à maintenir et plus évolutifs.</p><p>Que vous construisiez une salle de presse numérique, une plateforme de service à la clientèle, un assistant de recherche ou toute autre application alimentée par le LLM, l&#x27;examen attentif de vos besoins de coordination (A2A) et des exigences d&#x27;accès aux outils (MCP) vous mettra sur la voie de la réussite.</p><h2>Ressources supplémentaires</h2><ul><li><strong>Elasticsearch Agent Builder </strong><a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">: https://www.elastic.co/docs/solutions/search/elastic-agent-builder</a></li><li><strong>Spécification A2A</strong> <a href="https://a2a-protocol.org/latest/specification/">: https://a2a-protocol.org/latest/specification/</a></li><li><strong>Intégration A2A et MCP</strong> <a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">: https://a2a-protocol.org/latest/topics/a2a-and-mcp/</a></li><li><strong>Modèle de protocole de contexte</strong> <a href="https://modelcontextprotocol.io/">: https://modelcontextprotocol.io</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vous savez, pour le contexte - Partie III : La puissance de la recherche hybride dans l'ingénierie contextuelle]]></title>
            <description><![CDATA[Découvrez comment utiliser l'ingénierie contextuelle et la recherche hybride pour améliorer la précision des résultats de l'IA avec des agrégations, RBAC et des signaux sans contenu.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/context-engineering-hybrid-search-agentic-ai-accuracy</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/context-engineering-hybrid-search-agentic-ai-accuracy</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Woody Walton]]></dc:creator>
            <pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b82ff25242e4229cc20b218d9cc91c60cfd680bc-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Nous avons abordé la recherche hybride<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">(partie I</a>) et l&#x27;ingénierie contextuelle<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">(partie II)</a>; nous allons maintenant voir comment ces deux techniques fonctionnent ensemble pour fournir un contexte ciblé aux opérations de RAG et d&#x27;IA agentique.</p><h2>La recherche n&#x27;est pas morte, elle s&#x27;est simplement déplacée</h2><p>Nous sommes donc passés d&#x27;une recherche de contexte à l&#x27;aide d&#x27;une zone de texte et de l&#x27;utilisation des informations (le contexte) renvoyées pour construire les réponses nous-mêmes, à l&#x27;utilisation du langage naturel pour dire à un agent ce que nous voulons et lui permettre de rechercher et de compiler automatiquement la réponse pour nous. Nombreux sont ceux qui, dans le monde de la technologie, soulignent ce changement et proclament que &quot;la recherche est morte&quot; (certes, le monde du référencement et des mots publicitaires est en train de <a href="https://www.pewresearch.org/short-reads/2025/07/22/google-users-are-less-likely-to-click-on-links-when-an-ai-summary-appears-in-the-results/">changer</a>: les <a href="https://www.wired.com/story/goodbye-seo-hello-geo-brandlight-openai/">GEO</a>, par exemple), mais la recherche reste absolument essentielle pour les opérations agentiques - elle est simplement réalisée en grande partie à l&#x27;abri des regards par le biais d&#x27;outils.</p><p>Auparavant, les humains étaient les principaux arbitres de la pertinence subjective : chaque utilisateur a ses propres raisons d&#x27;effectuer une recherche, et son expérience personnelle influence la précision relative des résultats. Si nous voulons que les agents parviennent à la même conclusion (ou à une meilleure conclusion) que nous, nous devons nous assurer que les informations contextuelles auxquelles ils ont accès sont aussi proches que possible de notre intention subjective. Nous devons concevoir le contexte dans lequel nous fournissons les LLM en fonction de cet objectif !</p><h2>Générer du contexte avec la recherche hybride</h2><p>Je vous rappelle que la recherche hybride d&#x27;Elastic combine les points forts de la recherche traditionnelle par mot-clé (flexibilité syntaxique, précision des mots-clés et évaluation de la pertinence) avec la compréhension sémantique de la recherche par similarité vectorielle, et offre plusieurs techniques de reclassement. Cette synergie (il n&#x27;y a jamais eu d&#x27;usage plus vrai de ce mot !) permet d&#x27;obtenir des résultats très pertinents, avec des requêtes qui peuvent être beaucoup plus nuancées dans la manière dont elles ciblent le contenu. Il ne s&#x27;agit pas seulement d&#x27;appliquer la pertinence subjective à l&#x27;<em>une des</em> étapes de la recherche ; il s&#x27;agit en fait d&#x27;inclure la notation de la pertinence dans la première étape de la recherche, ainsi que tous les autres modes à la fois.</p><h3>Précision supérieure &amp; efficacité</h3><p>L&#x27;utilisation d&#x27;une plateforme de données capable de fournir des services de recherche, d&#x27;extraction et de reclassement distribués en tant que principal moteur de recherche contextuelle est très judicieuse. Vous pouvez utiliser une syntaxe d&#x27;interrogation avancée pour ajouter la composante manquante de l&#x27;intention subjective et filtrer le contenu qui pourrait distraire ou brouiller la valeur des informations contextuelles renvoyées. Vous pouvez sélectionner l&#x27;une des options syntaxiques individuelles disponibles ou combiner les modalités dans une recherche unique qui cible chaque type de données de la manière qu&#x27;elle comprend le mieux, puis les combiner ou les réordonner avec le reranking. Vous pouvez filtrer la réponse pour qu&#x27;elle ne contienne que les champs/valeurs que vous souhaitez, en évitant les données superflues. Au service des agents, cette souplesse de ciblage vous permet de créer des outils extrêmement précis dans la manière dont ils récupèrent le contexte.</p><h3>Raffinement du contexte (agrégations et signaux non liés au contenu)</h3><p>Les agrégations peuvent être particulièrement utiles pour façonner le contenu d&#x27;un outil dans la fenêtre contextuelle. Les agrégations fournissent naturellement des faits numériques sur la forme des données contextuelles renvoyées, ce qui permet aux LLM de raisonner plus facilement et avec plus de précision. Les agrégations pouvant être imbriquées hiérarchiquement, il est facile d&#x27;ajouter des détails à plusieurs niveaux pour que le mécanisme d&#x27;apprentissage tout au long de la vie génère une compréhension plus nuancée. Les agrégations peuvent également faciliter la gestion de la taille de la fenêtre contextuelle - vous pouvez facilement réduire le résultat d&#x27;une requête de 100 000 documents à quelques centaines de tokens d&#x27;informations agrégées.</p><p>Les signaux non liés au contenu sont les indicateurs inhérents à vos données qui vous donnent une vue d&#x27;ensemble de ce que vous regardez ; il s&#x27;agit des caractéristiques supplémentaires des résultats, comme la popularité, la fraîcheur, la géolocalisation, les catégories, la diversité des hôtes ou les fourchettes de prix. Ces éléments d&#x27;information peuvent être utiles à l&#x27;agent pour évaluer l&#x27;importance du contexte qu&#x27;il a reçu. Quelques exemples simples permettent d&#x27;illustrer au mieux ce propos :</p><ul><li><strong>Renforcer le contenu récemment publié et populaire</strong> - Imaginez que vous disposiez d&#x27;une base de connaissances contenant des articles. Vous souhaitez trouver des articles pertinents par rapport à la requête d&#x27;un utilisateur, mais vous voulez également favoriser les articles qui sont récents et qui ont été jugés utiles par d&#x27;autres utilisateurs (par exemple, qui ont un nombre élevé de &quot;likes&quot; ). Dans ce scénario, nous pouvons utiliser une recherche hybride pour trouver les articles pertinents, puis les classer en fonction de leur date de publication et de leur popularité.</li><li><strong>Recherche dans le domaine du commerce électronique avec ajustement des ventes et des stocks</strong> - Dans le cadre du commerce électronique, vous souhaitez montrer aux clients les produits qui correspondent à leur recherche, mais vous voulez également promouvoir les produits qui se vendent bien et qui sont en stock. Vous pouvez également déclasser les produits dont le stock est faible afin d&#x27;éviter la frustration des clients.</li><li><strong>Priorité aux problèmes de haute gravité dans un système de suivi des bogues</strong> - Pour une équipe de développement de logiciels, lorsqu&#x27;elle recherche des problèmes, il est essentiel de faire apparaître en premier les problèmes de haute gravité, de haute priorité et ceux qui ont été récemment mis à jour. Vous pouvez utiliser des signaux secondaires tels que &quot;criticité&quot; et &quot;le plus discuté&quot; pour pondérer les différents facteurs de manière indépendante, en veillant à ce que les questions les plus critiques et les plus activement discutées soient placées en tête.</li></ul><p>Ces exemples de requêtes et d&#x27;autres sont disponibles dans la <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/you-know-for-context/">page de contenu</a> Elasticsearch Labs qui les accompagne.</p><h3>Renforcement de la sécurité</h3><p>Un avantage essentiel de l&#x27;exploitation d&#x27;une couche de vitesse alimentée par la recherche telle qu&#x27;Elastic pour l&#x27;ingénierie contextuelle est son cadre de sécurité intégré. La plateforme d&#x27;Elastic garantit que le contexte fourni aux opérations d&#x27;IA agentique et générative respecte et protège les informations privées sensibles grâce à un contrôle d&#x27;accès granulaire basé sur les rôles (RBAC) et un contrôle d&#x27;accès basé sur les attributs (ABAC). Cela signifie que non seulement les requêtes sont traitées avec efficacité, mais aussi que les résultats sont filtrés en fonction des autorisations spécifiques de l&#x27;agent ou de l&#x27;utilisateur à l&#x27;origine de la demande.</p><p>Les agents s&#x27;exécutent en tant qu&#x27;utilisateur authentifié, de sorte que la sécurité est implicitement appliquée par le biais des fonctions de sécurité intégrées à la plateforme :</p><ul><li><strong>Permissions précises :</strong> Définissez l&#x27;accès au niveau du document, du champ ou même du terme, en veillant à ce que les agents d&#x27;intelligence artificielle ne reçoivent que les données qu&#x27;ils sont autorisés à consulter.</li><li><strong>Contrôle d&#x27;accès basé sur les rôles (RBAC) :</strong> Attribuer des rôles aux agents ou aux utilisateurs, en leur donnant accès à des ensembles de données ou à des fonctionnalités spécifiques en fonction des responsabilités qu&#x27;ils ont définies.</li><li><strong>Contrôle d&#x27;accès basé sur les attributs (ABAC) :</strong> Mettre en œuvre des politiques d&#x27;accès dynamiques basées sur les attributs des données, de l&#x27;utilisateur ou de l&#x27;environnement, permettant une sécurité hautement adaptable et consciente du contexte.</li><li><strong>Sécurité au niveau du document (DLS) et sécurité au niveau du champ (FLS) :</strong> Ces capacités garantissent que, même au sein d&#x27;un document récupéré, seules les parties autorisées sont visibles, empêchant ainsi l&#x27;exposition d&#x27;informations sensibles.</li><li><strong>Intégration avec la sécurité de l&#x27;entreprise :</strong> Intégration transparente avec les systèmes de gestion des identités existants (tels que LDAP, SAML, OIDC) afin d&#x27;appliquer des politiques de sécurité cohérentes dans l&#x27;ensemble de l&#x27;entreprise.</li></ul><p>En intégrant ces mesures de sécurité directement dans le mécanisme de récupération du contexte, Elastic agit comme un gardien sécurisé, garantissant que les agents d&#x27;intelligence artificielle opèrent dans des limites de données définies, empêchant l&#x27;exposition de données non autorisées et maintenant la conformité avec les réglementations en matière de confidentialité des données. Cela est primordial pour instaurer la confiance dans les systèmes d&#x27;IA agentique qui traitent des informations confidentielles ou exclusives.</p><p>En outre, l&#x27;utilisation d&#x27;une couche de vitesse unifiée sur les sources de données de l&#x27;entreprise permet d&#x27;alléger les charges de requêtes ad hoc inattendues sur ces référentiels que les outils agentiques créeraient. Vous disposez d&#x27;un lieu unique pour tout rechercher en temps quasi réel, et d&#x27;un lieu unique pour appliquer les contrôles de sécurité et de gouvernance.</p><h2>Outils hybrides basés sur la recherche</h2><p>La plateforme Elastic comporte certaines fonctionnalités de base (et d&#x27;<a href="https://www.elastic.co/blog/whats-new-elastic-9-2-0">autres sont en cours d&#x27;élaboration</a>) qui donnent un coup de fouet à l&#x27;ingénierie contextuelle. L&#x27;essentiel est que la plateforme offre une multitude de moyens de réaliser des choses, avec la flexibilité de s&#x27;adapter, de changer et d&#x27;étendre les méthodes au fur et à mesure que l&#x27;écosystème de l&#x27;IA progresse.</p><h3>Présentation de l&#x27;Agent Builder</h3><p>Elastic <a href="https://www.elastic.co/elasticsearch/agent-builder">Agent Builder</a> est notre première incursion dans le domaine des outils d&#x27;intelligence artificielle conçus pour dialoguer avec les données que vous stockez déjà dans Elastic. Agent Builder offre une interface de chat qui permet aux utilisateurs de créer et de gérer leurs propres agents et outils dans Kibana. Il est livré avec des serveurs MCP et A2A intégrés, des API programmatiques et un ensemble d&#x27;outils système prédéfinis pour l&#x27;interrogation et l&#x27;exploration des index Elasticsearch, ainsi que pour la génération de requêtes ES|QL à partir du langage naturel. Agent Builder vous permet de créer des outils personnalisés qui ciblent et sculptent les données contextuelles renvoyées à l&#x27;agent par le biais d&#x27;une syntaxe de requête <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> expressive.</p><p>Comment ES|QL effectue-t-il la recherche hybride ? La capacité de base est obtenue par la combinaison du type de champ <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a> <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork">et des</a>commandes<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fuse">FORK/FUSE (FUSE utilise</a> <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">par défaut RRF</a> pour fusionner les résultats de chaque fourchette). Voici un exemple simple de recherche de produit fictif :</p><pre><code>FROM products
| FORK
  (MATCH description "high performance gaming laptop" | EVAL search_type = "bm25"),
  (MATCH description_semantic "high performance gaming laptop" | EVAL search_type = "semantic")
| FUSE 
| LIMIT 20
| KEEP product_name, description, _score, search_type</code></pre><p>La clause <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval">EVAL</a> incluse dans chacune des branches FORK de l&#x27;exemple ci-dessus n&#x27;est pas strictement nécessaire ; elle n&#x27;est incluse que pour démontrer comment vous pouvez suivre la modalité de recherche à partir de laquelle un résultat donné a été retourné.</p><h3>Recherche de modèles</h3><p>Supposons que vous souhaitiez faire pointer vos propres outils agentiques externes vers votre déploiement Elastic. Au lieu d&#x27;ES|QL, vous souhaitez utiliser des extracteurs à plusieurs niveaux ou réutiliser la syntaxe DSL existante que vous avez développée, et vous voulez également pouvoir contrôler les entrées acceptées par la requête, la syntaxe utilisée pour exécuter la recherche et les champs renvoyés dans le résultat. Les <a href="https://www.elastic.co/docs/solutions/search/search-templates">modèles de recherche</a> permettent aux utilisateurs de définir des structures prédéfinies pour les modèles de recherche courants, ce qui améliore l&#x27;efficacité et la cohérence de la recherche de données. Ceci est particulièrement bénéfique pour les outils agentiques qui interagissent avec les API de recherche, car ils aident à normaliser le code standard et permettent une itération plus rapide de la logique de recherche. Et si vous devez modifier l&#x27;un de ces facteurs, il vous suffit de mettre à jour le modèle de recherche et voilà, les changements sont appliqués. Si vous cherchez un exemple de modèles de recherche en action avec des outils agentiques, jetez un coup d&#x27;œil au blog d&#x27;Elasticsearch Labs &quot;<a href="https://www.elastic.co/search-labs/blog/mcp-intelligent-search">MCP for intelligent search</a>&quot;, qui utilise un modèle de recherche derrière un appel d&#x27;outil à partir d&#x27;un serveur MCP externe.</p><h3>Flux de travail intégrés (FTW !)</h3><p>L&#x27;une des choses les plus difficiles à gérer dans notre nouveau monde d&#x27;IA agentique est la nature non déterministe des agents &quot;raisonnants&quot; semi-autonomes et autodirigés. L&#x27;ingénierie contextuelle est une discipline essentielle de l&#x27;IA agentique : il s&#x27;agit des techniques qui permettent de limiter les conclusions possibles de notre agent à ce que nous connaissons de la vérité de terrain. Même avec une fenêtre contextuelle très précise et pertinente (lorsque nous sortons du domaine des faits numériques), il nous manque toujours cette petite assurance que la réponse de l&#x27;agent est entièrement reproductible et fiable.</p><p>Lorsque vous soumettez plusieurs fois la même demande à un agent, les réponses peuvent être <em>essentiellement</em> les mêmes, avec <em>juste</em> une petite différence dans la réponse. C&#x27;est généralement bien pour les requêtes simples, peut-être à peine perceptible, et nous pouvons essayer de façonner le résultat à l&#x27;aide de techniques d&#x27;ingénierie contextuelle. Mais plus les tâches que nous demandons à nos agents sont complexes, plus il y a de chances qu&#x27;une ou plusieurs sous-tâches introduisent une variance qui modifie légèrement le résultat final. La situation s&#x27;aggravera probablement à mesure que nous commencerons à nous appuyer davantage sur les communications entre agents, et ces écarts deviendront cumulatifs. Cela confirme l&#x27;idée que les outils avec lesquels nos agents interagissent doivent être très souples et adaptables pour cibler précisément les données contextuelles, et qu&#x27;ils doivent répondre dans un format de sortie attendu. Il indique également que pour de nombreux cas d&#x27;utilisation, nous avons besoin de diriger les interactions entre l&#x27;agent et l&#x27;outil - c&#x27;est là que les flux de travail entrent en jeu !</p><p>Elastic disposera bientôt de flux de travail entièrement personnalisables, intégrés au cœur de la plateforme. Ces flux de travail pourront fonctionner avec des agents et des outils de manière bidirectionnelle, de sorte que les flux de travail pourront appeler des agents et des outils, et que les agents et les outils pourront appeler des flux de travail. L&#x27;intégration complète de ces capacités dans la même plateforme d&#x27;IA de recherche, où toutes vos données sont stockées, sera un facteur de transformation. Bientôt, très bientôt !</p><h3>Elastique comme la banque de mémoire unifiée</h3><p>En tant que plateforme de données distribuées conçue pour la recherche en temps quasi réel, Elastic remplit naturellement les fonctions de mémoire à long terme pour les systèmes d&#x27;IA agentique. Avec l&#x27;expérience de chat intégrée d&#x27;Agent Builder, nous disposons également d&#x27;un suivi et d&#x27;une gestion de la mémoire à court terme et de l&#x27;historique des chats. Et comme toute la plateforme est fondée sur l&#x27;API, il est extrêmement facile d&#x27;utiliser Elastic comme plateforme pour conserver les résultats contextuels d&#x27;un outil (et pouvoir s&#x27;y référer ultérieurement) qui pourraient dépasser la fenêtre contextuelle de l&#x27;agent ; cette technique est parfois appelée &quot;<a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=Agents%20can%20assemble%20understanding%20layer%20by%20layer%2C%20maintaining%20only%20what%27s%20necessary%20in%20working%20memory%20and%20leveraging%20note%2Dtaking%20strategies%20for%20additional%20persistence">prise de notes</a>&quot; dans les cercles de l&#x27;ingénierie contextuelle.</p><p>Le fait de disposer d&#x27;une mémoire à court terme et d&#x27;une mémoire à long terme sur la même plateforme de recherche présente de nombreux avantages intrinsèques : imaginez que vous puissiez utiliser les historiques de chat et les réponses contextuelles persistantes pour influencer sémantiquement les futures interactions de chat, ou pour effectuer une analyse des menaces, ou pour créer des produits de données persistants générés automatiquement à partir d&#x27;appels d&#x27;outils fréquemment répétés... Les possibilités sont infinies !</p><h2>Conclusion</h2><p>L&#x27;émergence de grands modèles de langage a modifié la façon dont nous pouvons faire correspondre le contenu et les méthodes que nous utilisons pour interroger nos données. Nous nous éloignons rapidement de notre monde actuel, où les humains effectuent les recherches, les considérations contextuelles et le raisonnement logique pour répondre à leurs propres questions, pour passer à un monde où ces étapes sont largement automatisées grâce à l&#x27;IA agentique. Pour que nous puissions faire confiance aux réponses générées que nous recevons, nous devons avoir l&#x27;assurance que l&#x27;agent a pris en compte <em>toutes les</em> informations <em>les plus pertinentes</em> (y compris le facteur de la pertinence subjective) pour générer sa réponse. Notre principale méthode pour rendre l&#x27;IA agentique digne de confiance consiste à ancrer les outils qui récupèrent un contexte supplémentaire grâce aux techniques de RAG et d&#x27;ingénierie contextuelle, mais la manière dont ces outils effectuent la <em>récupération initiale</em> peut être déterminante pour la précision de la réponse.</p><p>La plateforme d&#x27;IA Elastic Search offre la flexibilité et les avantages de la recherche hybride, ainsi que plusieurs fonctionnalités intégrées qui aident l&#x27;IA agentique en termes de précision, de performance et d&#x27;évolutivité ; en d&#x27;autres termes, Elastic est une plateforme fantastique pour plusieurs aspects de l&#x27;ingénierie contextuelle ! En normalisant la recherche de contexte via une plateforme de recherche, nous simplifions les opérations de l&#x27;outil agentique sur plusieurs fronts - et comme l&#x27;oxymore &quot;ralentir pour aller plus vite&quot;, la simplicité au niveau de la couche de génération de contexte signifie une IA agentique plus rapide et plus digne de confiance.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vous savez, pour le contexte - Partie II : L'IA agentique et le besoin d'ingénierie contextuelle]]></title>
            <description><![CDATA[Découvrez comment l'évolution des LLM vers l'IA agentique augmente le besoin d'ingénierie contextuelle pour résoudre les limites du contexte RAG et la gestion de la mémoire.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/context-engineering-llm-evolution-agentic-ai</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/context-engineering-llm-evolution-agentic-ai</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Woody Walton]]></dc:creator>
            <pubDate>Tue, 18 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6153ce399e5498841ef2ffa5363a3e2d2dea726d-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Avec ce <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">contexte</a> (relativement étendu) sur la façon dont les LLM ont changé les processus sous-jacents de la recherche d&#x27;informations, voyons comment ils ont également changé la façon dont nous interrogeons les données.</p><h2>Une nouvelle façon d&#x27;interagir avec les données</h2><p>L&#x27;IA générative (genAI) et l&#x27;IA agentique agissent différemment de la recherche traditionnelle. Alors que nous commencions à rechercher des informations par une recherche (&quot;laissez-moi chercher cela sur Google...&quot;), l&#x27;action initiale de l&#x27;IA générique et des agents se fait généralement par le biais d&#x27;un langage naturel saisi dans une interface de dialogue en ligne. L&#x27;interface de chat est une discussion avec un LLM qui utilise sa compréhension sémantique pour transformer notre question en une réponse distillée, une réponse résumée semblant provenir d&#x27;un oracle qui a une connaissance étendue de toutes sortes d&#x27;informations. Ce qui fait vraiment la différence, c&#x27;est la capacité du LLM à produire des phrases cohérentes et réfléchies qui rassemblent les éléments de connaissance qu&#x27;il fait apparaître - même s&#x27;ils sont inexacts ou totalement hallucinés, ils ont une certaine <a href="https://en.wikipedia.org/wiki/Truthiness">véracité</a>.</p><p>Cette vieille barre de recherche avec laquelle nous avons été tellement habitués à interagir peut être considérée comme le moteur RAG que nous utilisions lorsque <strong><em>nous</em></strong> étions nous-mêmes l&#x27;agent de raisonnement. Aujourd&#x27;hui, même les moteurs de recherche Internet transforment notre expérience de recherche lexicale bien connue en aperçus pilotés par l&#x27;IA qui répondent à la requête par un résumé des résultats, ce qui permet aux utilisateurs d&#x27;éviter de cliquer et d&#x27;évaluer eux-mêmes les résultats individuels.</p><h2>IA générative &amp; RAG</h2><p>L&#x27;IA générative tente d&#x27;utiliser sa compréhension sémantique du monde pour analyser l&#x27;intention subjective exprimée dans une demande de chat, puis utilise ses capacités d&#x27;inférence pour créer une réponse d&#x27;expert à la volée. L&#x27;interaction générative de l&#x27;IA comporte plusieurs parties : elle commence par l&#x27;entrée/la requête de l&#x27;utilisateur, les conversations précédentes dans la session de chat peuvent être utilisées comme contexte supplémentaire, et l&#x27;instruction qui indique au LLM comment raisonner et quelles sont les procédures à suivre pour construire la réponse. Les messages-guides ont évolué, passant d&#x27;une simple orientation du type &quot;, &quot;Expliquez-moi cela comme si j&#x27;étais un enfant de cinq ans&quot;, à des descriptions complètes de la manière de traiter les demandes. Ces décompositions comprennent souvent des sections distinctes décrivant les détails du personnage/rôle de l&#x27;IA, le raisonnement avant la génération/le processus de réflexion interne, les critères objectifs, les contraintes, le format de sortie, le public, ainsi que des exemples pour aider à démontrer les résultats attendus.</p><p>En plus de la requête de l&#x27;utilisateur et de l&#x27;invite du système, la génération augmentée de recherche (RAG) fournit des informations contextuelles supplémentaires dans ce que l&#x27;on appelle une &quot;fenêtre contextuelle&quot;. RAG a été un ajout essentiel à l&#x27;architecture ; c&#x27;est ce que nous utilisons pour informer le LLM des pièces manquantes dans sa compréhension sémantique du monde.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5b9671d5d07d4caefde372bb3188000754a91eed-1470x746.png" style="width: 100%;" /><p>Les fenêtres contextuelles peuvent être un peu <a href="https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html">tatillonnes</a> en ce qui concerne le contenu, l&#x27;emplacement et la quantité que vous leur donnez. Le contexte sélectionné est bien sûr très important, mais le rapport signal/bruit du contexte fourni est également important, de même que la longueur de la fenêtre.</p><h3>Trop peu d&#x27;informations</h3><p>Le fait de fournir trop peu d&#x27;informations dans une fenêtre de requête, d&#x27;invite ou de contexte peut entraîner des hallucinations, car le LLM ne peut pas déterminer avec précision le contexte sémantique correct à partir duquel générer une réponse. La similarité vectorielle de la taille des morceaux de documents pose également des problèmes - une question courte et simple peut ne pas correspondre sémantiquement aux documents riches et détaillés trouvés dans nos bases de connaissances vectorisées. Des techniques d&#x27;expansion des requêtes telles que <a href="https://medium.com/data-science/how-to-use-hyde-for-better-llm-rag-retrieval-a0aa5d0e23e8">Hypothetical Document Embeddings (HyDE)</a> ont été développées. Elles utilisent les LLM pour générer une réponse hypothétique qui est plus riche et plus expressive que la requête courte. Le danger ici, bien sûr, est que le document hypothétique est lui-même une hallucination qui éloigne encore plus le LLM du contexte correct.</p><h3>Trop d&#x27;informations</h3><p>Tout comme pour nous, un excès d&#x27;informations dans une fenêtre contextuelle peut submerger un MLD et le rendre confus quant aux éléments importants. Le débordement de contexte (ou &quot;<a href="https://research.trychroma.com/context-rot">pourriture de contexte</a>&quot;) affecte la qualité et les performances des opérations d&#x27;IA générative ; il a un impact considérable sur le &quot;budget d&#x27;attention&quot; du LLM (sa mémoire de travail) et dilue la pertinence parmi de nombreux éléments concurrents. Le concept de &quot;rotation du contexte&quot; comprend également l&#x27;observation selon laquelle les LLM ont tendance à avoir un <a href="https://alexandrabarr.beehiiv.com/p/context-windows">biais de position</a> - ils préfèrent le contenu au début ou à la fin d&#x27;une fenêtre contextuelle au contenu de la section centrale.</p><h3>Informations distrayantes ou contradictoires</h3><p>Plus la fenêtre contextuelle est grande, plus il y a de chances qu&#x27;elle contienne des informations superflues ou contradictoires qui peuvent distraire le LLM de la sélection et du traitement du contexte correct. D&#x27;une certaine manière, il s&#x27;agit d&#x27;un problème d&#x27;entrée et de sortie de déchets : le simple fait de déverser un ensemble de résultats de documents dans une fenêtre contextuelle donne au LLM beaucoup d&#x27;informations à mâcher (potentiellement trop), mais en fonction de la manière dont le contexte a été sélectionné, il y a une plus grande possibilité que des informations contradictoires ou non pertinentes s&#x27;infiltrent dans le système.</p><h2>IA agentique</h2><p>Je vous avais dit qu&#x27;il y avait beaucoup de terrain à couvrir, mais nous l&#x27;avons fait - nous parlons enfin de sujets liés à l&#x27;IA agentique ! L&#x27;IA agentique est une nouvelle utilisation très intéressante des interfaces de chat LLM qui développe la capacité de l&#x27;IA générative (peut-on déjà l&#x27;appeler &quot;ancienne&quot; ?) à synthétiser des réponses basées sur ses propres connaissances et sur les informations contextuelles que vous lui fournissez. Au fur et à mesure que l&#x27;IA générative gagnait en maturité, nous avons réalisé qu&#x27;il existait un certain niveau de tâches et d&#x27;automatisation que nous pouvions confier aux LLM, initialement reléguées à des activités fastidieuses à faible risque qui peuvent facilement être vérifiées/validées par un être humain. En peu de temps, ce champ d&#x27;application initial s&#x27;est élargi : une fenêtre de discussion LLM peut désormais être l&#x27;étincelle qui envoie un agent d&#x27;intelligence artificielle planifier, exécuter, évaluer et adapter son plan de manière itérative afin d&#x27;atteindre l&#x27;objectif spécifié. Les agents ont accès au raisonnement de leur LLM, à l&#x27;historique des discussions et à la mémoire de pensée (telle qu&#x27;elle est), et ils disposent également d&#x27;outils spécifiques qu&#x27;ils peuvent utiliser à cette fin. Nous voyons aussi maintenant des architectures qui permettent à un agent de haut niveau de fonctionner comme l&#x27;orchestrateur de plusieurs <a href="https://www.philschmid.de/the-rise-of-subagents">sous-agents</a>, chacun avec ses propres chaînes logiques, ses jeux d&#x27;instructions, son contexte et ses outils.</p><p>Les agents sont le point d&#x27;entrée d&#x27;un flux de travail essentiellement automatisé : ils sont autodirigés en ce sens qu&#x27;ils sont capables de discuter avec un utilisateur et d&#x27;utiliser ensuite la &quot;logique&quot; pour déterminer les outils dont ils disposent pour répondre à la question de l&#x27;utilisateur. Les outils sont généralement considérés comme passifs par rapport aux agents et construits pour effectuer un seul type de tâche. Les <em>types de</em> tâches qu&#x27;un outil pourrait accomplir sont en quelque sorte illimités (ce qui est vraiment passionnant !), mais l&#x27;une des principales tâches des outils est de rassembler des informations contextuelles qu&#x27;un agent doit prendre en compte lors de l&#x27;exécution de son flux de travail.</p><p>En tant que technologie, l&#x27;IA agentique en est encore à ses balbutiements et est sujette à l&#x27;équivalent LLM du trouble déficitaire de l&#x27;attention - elle oublie facilement ce qu&#x27;on lui a demandé de faire, et part souvent faire d&#x27;autres choses qui ne faisaient pas du tout partie du cahier des charges. Sous cette apparente magie, les capacités de &quot;raisonnement&quot; des LLM sont toujours basées sur la prédiction du prochain jeton le plus probable dans une séquence. Pour que le raisonnement (ou, un jour, l&#x27;intelligence artificielle générale (AGI)) devienne fiable et digne de confiance, nous devons être en mesure de vérifier que, lorsqu&#x27;on leur donne les informations correctes et les plus récentes, ils raisonnent de la manière que nous attendons d&#x27;eux (et nous donnent peut-être ce petit plus auquel nous n&#x27;aurions pas pensé nous-mêmes). Pour ce faire, les architectures agentiques devront être capables de communiquer clairement (protocoles), de respecter les flux de travail et les contraintes que nous leur imposons (garde-fous), de se rappeler où elles en sont dans une tâche (état), de gérer leur espace mémoire disponible et de valider que leurs réponses sont exactes et répondent aux critères de la tâche.</p><h2>Parlez-moi dans une langue que je peux comprendre</h2><p>Comme c&#x27;est souvent le cas dans les nouveaux domaines de développement (en particulier dans le monde des LLM), il existait initialement plusieurs approches pour les communications entre agents et outils, mais elles ont rapidement convergé vers le <a href="https://modelcontextprotocol.io/docs/getting-started/intro">protocole de contexte de modèle (MCP)</a> en tant que norme de facto. La définition du protocole de contexte de modèle est vraiment dans le nom - c&#x27;est le <strong>protocole</strong> qu&#x27;un <strong>modèle</strong> utilise pour demander et recevoir des informations <strong>contextuelles</strong>. MCP agit comme un adaptateur universel permettant aux agents LLM de se connecter à des outils et à des sources de données externes ; il simplifie et normalise les API de manière à ce que les différents cadres et outils LLM puissent facilement interopérer. Cela fait de MCP une sorte de point de pivot entre la logique d&#x27;orchestration et les invites du système données à un agent pour qu&#x27;il les exécute de manière autonome au service de ses objectifs, et les opérations envoyées à des outils pour qu&#x27;ils les exécutent de manière plus isolée (isolée au moins par rapport à l&#x27;agent qui en est à l&#x27;origine).</p><p>Cet écosystème est tellement nouveau que chaque direction d&#x27;expansion semble être une nouvelle frontière. Nous disposons de protocoles similaires pour les interactions entre agents<a href="https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/">(Agent2Agent (A2A)</a> natch !) ainsi que d&#x27;autres projets visant à améliorer la mémoire de raisonnement des agents<a href="https://venturebeat.com/ai/new-memory-framework-builds-ai-agents-that-can-handle-the-real-worlds">(ReasoningBank</a>), à sélectionner le meilleur serveur MCP pour le travail à effectuer<a href="https://arxiv.org/abs/2505.03275">(RAG-MCP</a>), et à utiliser l&#x27;analyse sémantique telle que la classification &quot;zero-shot&quot; et la détection de motifs sur les entrées et les sorties comme <a href="https://openai.github.io/openai-guardrails-python/">garde-fous</a> pour contrôler ce sur quoi un agent est autorisé à opérer.</p><p>Vous avez peut-être remarqué que l&#x27;intention sous-jacente de chacun de ces projets est d&#x27;améliorer la qualité et le contrôle des informations renvoyées à une fenêtre contextuelle agent/genAI ? Alors que l&#x27;écosystème de l&#x27;IA agentique continue de développer la capacité à mieux traiter ces informations contextuelles (pour les contrôler, les gérer et les exploiter), il sera toujours nécessaire d&#x27;extraire les informations contextuelles <em>les plus pertinentes</em> pour que l&#x27;agent puisse les mouliner.</p><h2>Bienvenue dans l&#x27;ingénierie contextuelle !</h2><p>Si vous êtes familier avec les termes de l&#x27;IA générative, vous avez probablement entendu parler de &quot;l&#x27;ingénierie des messages&quot; - à ce stade, il s&#x27;agit presque d&#x27;une pseudo-science à part entière. L&#x27;ingénierie des invites est utilisée pour trouver les moyens les meilleurs et les plus efficaces de décrire de manière proactive les comportements que vous souhaitez que le MLD utilise pour générer sa réponse. L&#x27;&quot;<a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">ingénierie du contexte</a>&quot; étend les techniques d&#x27;&quot;ingénierie de l&#x27;invite&quot; au-delà du côté de l&#x27;agent pour couvrir également les sources de contexte et les systèmes disponibles du côté des outils du protocole MCP, et comprend les thèmes généraux de la gestion, du traitement et de la génération du contexte :</p><ul><li><strong>Gestion du contexte </strong>- liée au maintien de l&#x27;efficacité de l&#x27;état et du contexte dans des flux de travail agentiques de longue durée et/ou plus complexes. Planification itérative, suivi et orchestration des tâches et de l&#x27;utilisation des outils pour atteindre les objectifs de l&#x27;agent. En raison du &quot;budget d&#x27;attention&quot; limité dont disposent les agents, la gestion du contexte concerne principalement les techniques qui permettent d&#x27;affiner la fenêtre contextuelle afin de capturer à la fois la portée la plus complète et les éléments les plus importants du contexte (sa précision par rapport à son rappel !). Les techniques comprennent la compression, le résumé et la persistance du contexte des étapes précédentes ou des appels d&#x27;outils pour faire de la place dans la mémoire de travail pour le contexte supplémentaire des étapes suivantes.</li><li><strong>Traitement du contexte </strong>- Les étapes logiques et, espérons-le, essentiellement programmatiques visant à intégrer, normaliser ou affiner le contexte acquis à partir de sources disparates afin que l&#x27;agent puisse raisonner sur l&#x27;ensemble du contexte d&#x27;une manière quelque peu uniforme. Le travail sous-jacent consiste à faire en sorte que le contexte provenant de toutes les sources (invites, RAG, mémoire, etc.) soit consommé par l&#x27;agent le plus efficacement possible. </li><li><strong>Génération de contexte </strong>- Si le traitement du contexte consiste à rendre le contexte récupéré utilisable par l&#x27;agent, alors la génération de contexte donne à l&#x27;agent la possibilité de demander et de recevoir ces informations contextuelles supplémentaires à volonté, mais aussi avec des contraintes.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4a8240e1eb078b2294b8d981b9caa8593589cac4-1600x900.png" style="width: 100%;" /><p>Les différents éphémères des applications de chat du LLM correspondent directement (et parfois de manière redondante) à ces fonctions de haut niveau de l&#x27;ingénierie contextuelle :</p><ul><li><strong>Instructions / invite du système</strong> - Les invites constituent l&#x27;échafaudage de la manière dont l&#x27;activité générative (ou agentique) de l&#x27;IA orientera sa réflexion vers la réalisation de l&#x27;objectif de l&#x27;utilisateur. Les messages-guides constituent un contexte à part entière ; il ne s&#x27;agit pas seulement d&#x27;instructions tonales - ils comprennent aussi souvent une logique d&#x27;exécution des tâches et des règles telles que &quot;réfléchir étape par étape&quot; ou &quot;respirer profondément&quot; avant de répondre afin de s&#x27;assurer que la réponse répond pleinement à la demande de l&#x27;utilisateur. Des tests récents ont montré que les langages de balisage sont très efficaces pour encadrer les différentes parties d&#x27;une invite, mais il faut également veiller à calibrer les instructions de manière à ce qu&#x27;elles soient à la fois trop vagues et trop spécifiques ; nous voulons donner suffisamment d&#x27;instructions pour que le LLM trouve le bon contexte, mais sans être trop prescriptif au point de passer à côté d&#x27;idées inattendues.</li><li><strong>Mémoire à court terme</strong> (état/historique) - La mémoire à court terme correspond essentiellement aux interactions de la session de chat entre l&#x27;utilisateur et le LLM. Ils sont utiles pour affiner le contexte lors des sessions en direct et peuvent être sauvegardés pour être retrouvés et poursuivis ultérieurement. </li><li><strong>Mémoire à long terme</strong> - La mémoire à long terme doit être constituée d&#x27;informations utiles pour plusieurs sessions. Et il ne s&#x27;agit pas seulement de bases de connaissances spécifiques à un domaine auxquelles on accède par le biais de RAG ; des recherches récentes utilisent les résultats de demandes d&#x27;IA agentique/générative antérieures pour apprendre et se référer aux interactions agentiques actuelles. Certaines des innovations les plus intéressantes dans le domaine de la mémoire à long terme sont liées à l&#x27;ajustement de la manière dont l&#x27;état est <a href="https://steve-yegge.medium.com/introducing-beads-a-coding-agent-memory-system-637d7d92514a">stocké et relié</a> afin que les agents puissent reprendre là où ils se sont arrêtés. </li><li><strong>Sortie structurée</strong> - La cognition nécessite un effort, il n&#x27;est donc pas surprenant que même avec des capacités de raisonnement, les LLM (tout comme les humains) veulent dépenser moins d&#x27;effort lorsqu&#x27;ils pensent, et en l&#x27;absence d&#x27;une API ou d&#x27;un protocole défini, avoir une carte (un schéma) sur la façon de lire les données renvoyées par un appel d&#x27;outil est extrêmement utile. L&#x27;inclusion de <a href="https://platform.openai.com/docs/guides/structured-outputs?lang=javascript">sorties structurées</a> dans le cadre agentique contribue à rendre ces interactions machine-machine plus rapides et plus fiables, en réduisant les besoins d&#x27;analyse.</li><li><strong>Outils disponibles</strong> - Les outils peuvent faire toutes sortes de choses, de la collecte d&#x27;informations supplémentaires (par exemple, en émettant des requêtes RAG vers les référentiels de données de l&#x27;entreprise, ou par le biais d&#x27;API en ligne) à l&#x27;exécution d&#x27;actions automatisées au nom de l&#x27;agent (comme la réservation d&#x27;une chambre d&#x27;hôtel sur la base des critères de la demande de l&#x27;agent). Les outils peuvent également être des sous-agents disposant de leur propre chaîne de traitement agentique. </li><li><strong>Retrieval Augmented Generation (RAG)</strong> - J&#x27;aime beaucoup la description de RAG en tant qu&#x27;&quot;intégration dynamique des connaissances&quot;. Comme décrit précédemment, le RAG est la technique permettant de fournir les informations supplémentaires auxquelles le LLM n&#x27;a pas eu accès lors de sa formation, ou bien il s&#x27;agit d&#x27;une réitération des idées que nous pensons être les plus importantes pour obtenir la bonne réponse - celle qui est la plus pertinente par rapport à notre requête subjective.</li></ul><h2>Une puissance cosmique phénoménale, un espace de vie minuscule !</h2><p>L&#x27;IA agentique a tant de nouveaux domaines fascinants et passionnants à explorer ! Il y a encore beaucoup de problèmes traditionnels de recherche et de traitement de données à résoudre, mais aussi de toutes nouvelles catégories de défis qui commencent seulement à être exposés à la lumière du jour dans la nouvelle ère des LLM. Bon nombre des problèmes immédiats auxquels nous sommes confrontés aujourd&#x27;hui sont liés à l&#x27;ingénierie contextuelle, c&#x27;est-à-dire au fait de fournir aux MFR les informations contextuelles supplémentaires dont ils ont besoin sans surcharger leur espace de mémoire de travail, qui est limité.</p><p>La flexibilité des agents semi-autonomes ayant accès à un ensemble d&#x27;outils (et à d&#x27;autres agents) donne lieu à tant de nouvelles idées pour la mise en œuvre de l&#x27;IA qu&#x27;il est difficile d&#x27;imaginer les différentes façons dont nous pourrions assembler les pièces du puzzle. La plupart des recherches actuelles s&#x27;inscrivent dans le domaine de l&#x27;ingénierie contextuelle et se concentrent sur la construction de structures de gestion de la mémoire capables de gérer et de suivre de plus grandes quantités de contexte. En effet, les problèmes de réflexion approfondie que nous voulons vraiment que les LLM résolvent présentent une complexité accrue et des étapes de réflexion plus longues et multiphases, où la mémorisation est extrêmement importante.</p><p>Une grande partie de l&#x27;expérimentation en cours dans le domaine consiste à essayer de trouver la gestion optimale des tâches et les configurations d&#x27;outils pour alimenter la gueule de l&#x27;agent. Chaque appel d&#x27;outil dans la chaîne de raisonnement d&#x27;un agent entraîne un coût cumulatif, à la fois en termes de calcul pour exécuter la fonction de l&#x27;outil et d&#x27;impact sur la fenêtre contextuelle limitée. Certaines des dernières techniques de gestion du contexte pour les agents LLM ont provoqué des effets en chaîne involontaires tels que l&#x27;&quot;<a href="https://venturebeat.com/ai/ace-prevents-context-collapse-with-evolving-playbooks-for-self-improving-ai">effondrement du contexte</a>&quot;, où la compression/le résumé du contexte accumulé pour les tâches de longue durée entraîne <em>trop</em> de pertes. Le résultat souhaité est de disposer d&#x27;outils qui renvoient un contexte succinct et précis, sans que des informations superflues ne viennent empiéter sur l&#x27;espace mémoire précieux de la fenêtre de contexte.</p><h3>Tant/trop de possibilités</h3><p>Nous voulons une séparation des tâches avec la possibilité de réutiliser les outils/composants, il est donc tout à fait logique de créer des outils agentiques dédiés pour se connecter à des sources de données spécifiques - chaque outil peut se spécialiser dans l&#x27;interrogation d&#x27;un type de référentiel, d&#x27;un type de flux de données, ou même d&#x27;un cas d&#x27;utilisation. Mais attention : dans le but de gagner du temps/de l&#x27;argent/de prouver que quelque chose est possible, la tentation sera grande d&#x27;utiliser les MLD comme outil de fédération... Essayez de ne pas le faire, nous sommes déjà passés par <a href="https://www.elastic.co/pdf/elastic-distributed-not-federated-search.pdf">là</a>! La recherche fédérée agit comme un &quot;traducteur universel&quot; qui convertit une requête entrante dans la syntaxe que le référentiel distant comprend, et qui doit ensuite rationaliser les résultats provenant de sources multiples en une réponse cohérente. La fédération en tant que technique <em>fonctionne</em> <em>bien</em> à petite échelle, mais à grande échelle et surtout lorsque les données sont multimodales, la fédération tente de combler des lacunes qui sont tout simplement trop importantes.</p><p>Dans le monde agentique, l&#x27;agent serait le fédérateur et les outils (par l&#x27;intermédiaire de MCP) seraient les connexions définies manuellement vers des ressources disparates. L&#x27;utilisation d&#x27;outils dédiés pour accéder à des sources de données non connectées peut sembler être une nouvelle façon puissante d&#x27;unir dynamiquement différents flux de données sur la base d&#x27;une requête, mais l&#x27;utilisation d&#x27;outils pour poser la même question à plusieurs sources finira probablement par causer plus de problèmes qu&#x27;elle n&#x27;en résoudra. Chacune de ces sources de données est probablement constituée de différents types de référentiels, chacun ayant ses propres capacités de récupération, de classement et de sécurisation des données qu&#x27;il contient. Ces écarts ou &quot;décalages d&#x27;impédance&quot; entre les référentiels augmentent bien entendu la charge de traitement. Ils peuvent également introduire des informations ou des signaux contradictoires, où quelque chose d&#x27;apparemment inoffensif comme un décalage de notation peut perturber considérablement l&#x27;importance accordée à un élément de contexte renvoyé, et affecter la pertinence de la réponse générée en fin de compte.</p><h3>Le changement de contexte est également difficile pour les ordinateurs</h3><p>Lorsque vous envoyez un agent en mission, sa première tâche consiste souvent à trouver toutes les données pertinentes auxquelles il a accès. Tout comme pour les humains, si chaque source de données à laquelle l&#x27;agent se connecte fournit des réponses dissemblables et désagrégées, il y aura une charge cognitive (mais pas exactement du même type) associée à l&#x27;extraction des éléments contextuels saillants du contenu récupéré. Cela prend du temps/du calcul, et chaque petit morceau s&#x27;additionne dans la chaîne logique agentique. Cela conduit à la conclusion que, à l&#x27;instar de ce qui est discuté pour <a href="https://blog.cloudflare.com/code-mode/">MCP</a>, la plupart des outils agentiques devraient plutôt se comporter comme des API - des fonctions isolées avec des entrées et des sorties connues, réglées pour répondre aux besoins de différents types d&#x27;agents. Ils <a href="https://arxiv.org/html/2501.12372v5">parviennent</a> beaucoup mieux à relier les points sémantiques, en particulier lorsqu&#x27;il s&#x27;agit d&#x27;une tâche telle que la traduction du langage naturel en syntaxe structurée, lorsqu&#x27;ils disposent d&#x27;un schéma auquel se référer (RTFM en effet !).</p><h2>7ème manche !</h2><p>Nous avons maintenant abordé l&#x27;<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">impact des LLM sur la recherche et l&#x27;interrogation de données</a>, ainsi que la manière dont la fenêtre de discussion évolue vers l&#x27;expérience de l&#x27;IA agentique. Mettons les deux sujets ensemble et voyons comment nous pouvons utiliser nos nouvelles capacités de recherche et d&#x27;extraction pour améliorer nos résultats en matière d&#x27;ingénierie contextuelle. En route pour la <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy">troisième partie : la puissance de la recherche hybride dans l&#x27;ingénierie contextuelle</a>!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment déployer Elasticsearch sur Azure AKS Automatic]]></title>
            <description><![CDATA[Découvrez comment déployer Elasticsearch avec Kibana sur Azure en utilisant AKS Automatic et ECK pour une configuration Elasticsearch partiellement gérée.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-azure-aks-automatic-deployment</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-azure-aks-automatic-deployment</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Eduard Martin]]></dc:creator>
            <pubDate>Fri, 14 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4d821659a606e04390b09215e9a0d32eb01f0d1b-854x489.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Cet article fait partie d&#x27;une série où nous apprendrons comment installer Elasticsearch en utilisant différentes infrastructures.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0a65ca8b62fd8a42d7751b8f4bf057e33d877304-940x458.png" style="width: 100%;" /><p>ECK demande nettement plus d&#x27;efforts que les solutions Elastic Cloud basées sur la Marketplace, mais il est plus automatisé que le déploiement de VM par vous-même, car l&#x27;opérateur Kubernetes s&#x27;occupera de l&#x27;orchestration du système et de la mise à l&#x27;échelle des nœuds.</p><p>Cette fois, nous allons travailler avec le service Azure Kubernetes (AKS), en utilisant Automatic. Dans les autres articles, vous apprendrez à utiliser <a href="https://www.elastic.co/search-labs/blog/azure-elasticsearch-vm-deployment">Azure VM</a> et <a href="https://www.elastic.co/search-labs/blog/deploy-elasticsearch-azure-marketplace">Azure Marketplace</a>.</p><h2>Qu&#x27;est-ce qu&#x27;AKS Automatic ?</h2><p><a href="https://learn.microsoft.com/en-us/azure/aks/intro-aks-automatic">Azure Kubernetes Service (AKS) </a>gère automatiquement la configuration des clusters, alloue dynamiquement les ressources et intègre les meilleures pratiques de sécurité tout en préservant la flexibilité de Kubernetes, ce qui permet aux développeurs de passer d&#x27;une image de conteneur à une application déployée en quelques minutes.</p><p>AKS Automatic supprime la plupart des frais généraux liés à la gestion des clusters et offre un bon équilibre entre simplicité et flexibilité. Le bon choix dépend de votre cas d&#x27;utilisation, mais la décision est plus facile à prendre si vous prévoyez de le faire :</p><ul><li><strong>Déployer un environnement de test : </strong>Le déploiement est rapide et simple, ce qui le rend idéal pour les expériences rapides ou les clusters de courte durée.</li><li><strong>Travaillez sans exigences strictes en matière de VM, de stockage ou de réseau : </strong>AKS Automatic fournit des valeurs par défaut prédéfinies, de sorte que si celles-ci correspondent à vos besoins, vous n&#x27;avez pas besoin d&#x27;une configuration supplémentaire.</li><li><strong>Commencez avec Kubernetes pour la première fois : </strong>En prenant en charge une grande partie de la configuration du cluster, AKS Automatic réduit la courbe d&#x27;apprentissage et permet aux équipes de se concentrer sur leurs applications.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2e4c09b8c5e0ce5e8ea9c369626a373b7030a5ba-854x489.png" style="width: 100%;" /><p>Pour Elasticsearch, nous allons utiliser <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes </a>(ECK), qui est l&#x27;opérateur officiel d&#x27;Elastic Kubernetes qui simplifie l&#x27;orchestration des déploiements Kubernetes de la pile Elastic.</p><h2>Comment configurer AKS Automatic</h2><p>1. Connectez-vous au <a href="https://azure.microsoft.com/">portail Microsoft Azure</a>.</p><p>2. En <strong>haut à droite, </strong>cliquez sur<strong> </strong>sur le bouton<strong> Cloud Shell</strong> pour accéder à la console et déployer le cluster AKS à partir de là. Vous pouvez également utiliser <a href="https://learn.microsoft.com/en-us/azure/cloud-shell/overview">Azure Cloud Shell</a>.</p><p><strong><em>N&#x27;oubliez pas de mettre à jour l&#x27;identifiant du projet avec le vôtre pendant le tutoriel.</em></strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0aa60605777c0a6e3aef8faa4e54388c2cb582c8-624x495.png" style="width: 100%;" /><p><em>L&#x27;ouverture de l&#x27;AKS devrait ressembler à la capture d&#x27;écran ci-dessus.</em></p><p>3. Installez l&#x27;extension aks-preview Azure CLI. Cette version preview nous permettra de sélectionner <code>--sku automatic</code> lors de la création du cluster, ce qui activera la fonction AKS Automatic.</p><pre><code>az extension add --name aks-preview</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2e05ab67fc554c5fb5208683c179fdeaeadd95db-624x56.png" style="width: 100%;" /><p><em>Si vous voyez ce message, cela signifie que l&#x27;extension AKS a été installée correctement.</em></p><p>4. Enregistrer les<a href="https://learn.microsoft.com/en-us/azure/azure-app-configuration/concept-feature-management"> indicateurs de caractéristiques à</a> l&#x27;aide de la commande <code>az feature register</code></p><pre><code>az feature register --namespace Microsoft.ContainerService --name AutomaticSKUPreview</code></pre><p><em>Vous verrez les détails de l&#x27;abonnement de fonctionnalité que nous venons de créer :</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3aca710c1f312ba91de461638e518386919ec722-801x138.png" style="width: 100%;" /><p>Vérifiez l&#x27;état de l&#x27;enregistrement jusqu&#x27;à ce qu&#x27;il passe de &quot;<strong><em>En cours d&#x27;enregistrement</em></strong>&quot; à &quot;<strong><em>Enregistré</em></strong>&quot;. L&#x27;enregistrement peut prendre quelques minutes.</p><pre><code>az feature show --namespace Microsoft.ContainerService --name AutomaticSKUPreview</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/186486b08ab8e1c372efaff50f10cbddeaf4e0cd-844x177.png" style="width: 100%;" /><p>Exécutez <code>az provider register</code> pour propager les modifications.</p><pre><code>az provider register --namespace Microsoft.ContainerService</code></pre><p>5. Créer un groupe de ressources</p><p>Un groupe de ressources est un groupe logique de ressources Azure à gérer et à déployer.</p><pre><code>az group create --name elastic-resource --location eastus</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d1952269e97d94f914020754bd02702f9eafd037-770x212.png" style="width: 100%;" /><p>6. Créez un cluster Autopilot. Nous le nommerons <strong><em>myAKSAutomaticCluster </em></strong>et utiliserons le groupe de ressources que nous venons de créer. Assurez-vous d&#x27;avoir <strong><em>16 vCPUs</em></strong> disponibles sur l&#x27;une des tailles de VM suivantes : <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dpsv5-series">Standard_D4pds_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dldsv5-series">Standard_D4lds_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dadsv5-series">Standard_D4ads_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/ddsv5-series">Standard_D4ds_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/ddv5-series">Standard_D4d_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/ddv4-series">Standard_D4d_v4</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dsv3-series">Standard_DS3_v2</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/memory-optimized/dv2-dsv2-series-memory">Standard_DS12_v2</a> pour qu&#x27;AKS alloue des ressources.</p><pre><code>az aks create \
    --resource-group elastic-resource \
    --name myAKSAutomaticCluster \
    --sku automatic \
    --generate-ssh-keys</code></pre><p>* <em>Si vous obtenez des  erreurs <code>MissingSubscriptionRegistration</code></em>, revenez à l&#x27;étape 4 avec les abonnements manquants. Par exemple , <em><code>The subscription is not registered to use namespace &#x27;<strong>microsoft.insights</strong>&#x27;</code> nécessite l&#x27;exécution de <code>az provider register --namespace Microsoft.Insights.</code></em></p><p>Suivez la connexion interactive :</p><p><em>Un message demandant d&#x27;exécuter &quot;az login&quot; s&#x27;affiche. Vous devez exécuter cette commande et attendre.</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/14c0238f755fe6347519e69d3cb28c0fa52ec044-775x203.png" style="width: 100%;" /><p>7. Attendez qu&#x27;il soit prêt. Il faut environ 10 minutes pour le créer.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb00c3ad18f884f47db6645b196808ebec07c1fc-797x177.png" style="width: 100%;" /><p>8. Configurer l&#x27;accès à la ligne de commande kubectl.</p><pre><code>az aks get-credentials --resource-group elastic-resource --name myAKSAutomaticCluster</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5136d72a5d455345b0b6205bb232c4bdf7762998-793x52.png" style="width: 100%;" /><p><em>Notez que l&#x27;extension que nous avons installée active AKS Automatic.</em></p><p>9. Confirmez que les nœuds ont été déployés.</p><pre><code>kubectl get nodes</code></pre><p>Vous verrez un message d&#x27;erreur interdit ; copiez l&#x27;identifiant de l&#x27;utilisateur dans le message d&#x27;erreur.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d6c021fa54f4db00d2d795f5ba9b5a93376d03cd-818x47.png" style="width: 100%;" /><p>10. Ajoutez votre utilisateur au contrôle d&#x27;accès AKS.</p><p>Obtenir l&#x27;ID AKS. Copie la sortie de la commande.</p><pre><code>az aks show --resource-group elastic-resource  --name myAKSAutomaticCluster --query id --output tsv</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/22a1cdc538bd379812a752c6a368a0651000abb8-810x36.png" style="width: 100%;" /><p>Créez une attribution de rôle en utilisant l&#x27;identifiant AKS et l&#x27;identifiant principal de votre utilisateur.</p><pre><code>az role assignment create --role "Azure Kubernetes Service RBAC Cluster Admin" --assignee <PRINCIPAL_ID> --scope <AKS_ID></code></pre><p>11. Essayez de confirmer que les nœuds ont été déployés à nouveau.</p><pre><code>kubectl get nodes</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c9b3a5c1cc540ef732c3e7f60b0a973bdbd0b6fd-617x99.png" style="width: 100%;" /><p>12. Installez l&#x27;opérateur Elastic Cloud on the Kubernetes (ECK).</p><pre><code># Install ECK Custom Resource Definitions
kubectl create -f https://download.elastic.co/downloads/eck/2.16.1/crds.yaml

# Install the ECK operator
kubectl apply -f https://download.elastic.co/downloads/eck/2.16.1/operator.yaml</code></pre><p>13. Créons une instance Elasticsearch à nœud unique avec les valeurs par défaut.</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
EOF</code></pre><p>Nous avons désactivé <code>nmap</code> parce que la machine AKS par défaut a une valeur <code>vm.max_map_count</code> trop faible. Sa désactivation n&#x27;est pas recommandée pour la production, mais l&#x27;augmentation de la valeur de <code>vm.max_map_count</code>. Pour en savoir plus <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/virtual-memory">, cliquez ici.</a></p><p>14. Déployons également un cluster Kibana à nœud unique. Pour Kibana, nous allons ajouter un équilibreur de charge, qui nous donnera une IP externe que nous pouvons utiliser pour atteindre Kibana depuis notre appareil.</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
EOF</code></pre><p>Par défaut, AKS Automatic configure l&#x27;équilibreur de charge comme étant public ; vous pouvez modifier ce comportement en définissant l&#x27;annotation des métadonnées :</p><p><code>service.beta.kubernetes.io/azure-load-balancer-internal: &quot;true&quot;</code></p><p>15. Vérifiez que vos pods fonctionnent.</p><pre><code>kubectl get pods</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/213a897176c0af6cea19c7c777cfaf8734e3ee6e-616x84.png" style="width: 100%;" /><p>16. Vous pouvez également lancer <code>kubectl get elasticsearch</code> et <code>kubectl get kibana</code> pour obtenir des statistiques plus spécifiques comme la version d&#x27;Elasticsearch, les nœuds et l&#x27;état de santé.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c84988e725ef892eddd8fb7e5a03d58c35a8f9d6-470x62.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/452dd03d314cd00c8a3c19e19862b968592a0435-415x62.png" style="width: 100%;" /><p>17. Accédez à vos services.</p><pre><code>kubectl get svc</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b2d3e8f368be22b89aa2ed4d4d514f97dd6cbabd-624x115.png" style="width: 100%;" /><p>Cela vous montrera l&#x27;<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/accessing-services">URL externe</a> de Kibana sous EXTERNAL-IP. Le provisionnement de l&#x27;équilibreur de charge peut prendre quelques minutes. <strong><em>Copier la valeur de EXTERNAL-IP.</em></strong></p><p>18. Obtenez le mot de passe Elasticsearch pour l&#x27;utilisateur &#x27;elastic&#x27; :</p><pre><code>kubectl get secret quickstart-es-elastic-user -o=jsonpath='{.data.elastic}' | base64 --decode</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bd74537f8c35c4e027c518913fdb0a0524621d56-624x31.png" style="width: 100%;" /><p>19. <strong>Accédez à Kibana</strong> via votre navigateur :</p><p>a. URL : https://&lt;EXTERNAL_IP&gt;:5601</p><p>b. Nom d&#x27;utilisateur:elastic</p><p>c. Mot de passe:c44A295CaEt44D6xIzN6Zs5m (de l&#x27;étape précédente)</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a601112527d80721b292328ed8da58386d2837eb-463x503.png" style="width: 100%;" /><p>20. Lorsque vous accédez à Elastic Cloud à partir de votre navigateur, vous verrez l&#x27;écran de bienvenue.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f91e71fa961d215a8d886601d1a9fc5c452ce329-1999x1256.png" style="width: 100%;" /><p>Si vous souhaitez modifier les spécifications du cluster Elasticsearch, comme changer ou redimensionner les nœuds, vous pouvez appliquer à nouveau le manifeste YML avec les nouveaux paramètres :</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
    - name: default
      count: 2
      config:
        node.store.allow_mmap: false
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              resources:
                requests:
                  memory: 1.5Gi
                  cpu: 2
                limits:
                  memory: 1.5Gi
                  cpu: 2
EOF</code></pre><p>Dans cet exemple, nous allons ajouter un nœud supplémentaire et modifier la RAM et la CPU. Comme vous pouvez le voir, <code>kubectl get elasticsearch</code> affiche maintenant 2 nœuds :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4b139a476b50933d45d99e09479112817964f76a-624x60.png" style="width: 100%;" /><p>Il en va de même pour Kibana :</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
  podTemplate:
    spec:
      containers:
        - name: kibana
          env:
            - name: NODE_OPTIONS
              value: "--max-old-space-size=1024"
          resources:
            requests:
              memory: 0.5Gi
              cpu: 0.5
            limits:
              memory: 1Gi
              cpu: 1
EOF</code></pre><p>Nous pouvons ajuster le CPU/RAM du conteneur ainsi que l&#x27;utilisation de la mémoire de <a href="https://nodejs.org/">Node.js </a><a href="https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib">(max-old-space-size)</a>.</p><p>N&#x27;oubliez pas que les <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/volume-claim-templates">créances en volume existantes ne peuvent pas être réduites</a>. Après avoir appliqué la mise à jour, l&#x27;opérateur effectuera les changements avec un minimum de temps d&#x27;interruption.</p><p>N&#x27;oubliez pas de supprimer la grappe lorsque vous avez terminé les tests afin d&#x27;éviter des coûts inutiles.</p><pre><code>az aks delete --name myAKSAutomaticCluster --resource-group elastic-resource</code></pre><h2>Conclusion</h2><p>L&#x27;utilisation d&#x27;Azure AKS Automatic avec ECK fournit une solution équilibrée pour le déploiement d&#x27;Elasticsearch et de Kibana : elle réduit la complexité opérationnelle, assure une mise à l&#x27;échelle et des mises à jour automatisées, et tire parti de la flexibilité de Kubernetes. Cette approche est idéale pour les équipes qui souhaitent un processus de déploiement fiable, reproductible et maintenable sans avoir à gérer manuellement chaque détail de l&#x27;infrastructure, ce qui en fait un choix pratique pour les environnements de test et de production.</p><h2>Étapes suivantes</h2><p>Si vous souhaitez en savoir plus sur Kubernetes, vous pouvez consulter la documentation officielle ici :</p><ul><li><a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud sur Kubernetes | Elastic Docs</a></li><li><a href="https://learn.microsoft.com/en-us/azure/aks/intro-aks-automatic">Introduction à Azure Kubernetes Service (AKS) Automatique (preview)</a></li><li><a href="https://azure.github.io/AKS/2024/05/22/aks-automatic">AKS Automatic - Blog d&#x27;AKS Engineering</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Création d'une salle de presse LLM Agent avec le protocole A2A et MCP dans Elasticsearch : Partie I]]></title>
            <description><![CDATA[Explorer les concepts du protocole A2A et du MCP dans le cadre d'un exemple pratique de salle de presse où des agents LLM spécialisés collaborent à la recherche, à la rédaction, à l'édition et à la publication d'articles de presse.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/a2a-protocol-mcp-llm-agent-newsroom-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/a2a-protocol-mcp-llm-agent-newsroom-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Justin Castilla]]></dc:creator>
            <pubDate>Thu, 13 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9f938d8e2f0fdf7509edf028816c48bdbc8b3fc7-1600x900.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Introduction</h2><p>Les systèmes actuels soutenus par le LLM évoluent rapidement au-delà des applications à modèle unique vers des réseaux complexes où des agents spécialisés travaillent ensemble pour accomplir des tâches que l&#x27;informatique moderne n&#x27;aurait jamais cru possibles auparavant. Au fur et à mesure que ces systèmes gagnent en complexité, l&#x27;infrastructure permettant la communication entre les agents et l&#x27;accès aux outils devient l&#x27;objectif principal du développement. Deux approches complémentaires sont apparues pour répondre à ces besoins : Les protocoles <strong>Agent2Agent (A2A)</strong> pour la coordination multi-agents, et le <strong>Model Context Protocol (MCP)</strong> pour l&#x27;accès standardisé aux outils et aux ressources.</p><p>Comprendre quand utiliser l&#x27;un et l&#x27;autre en harmonie ou non peut avoir un impact significatif sur l&#x27;évolutivité, la maintenabilité et l&#x27;efficacité de vos applications. Cet article explore les concepts et les implémentations de l&#x27;<strong>A2A</strong> dans l&#x27;exemple pratique d&#x27;une salle de presse numérique, où des agents LLM spécialisés collaborent à la recherche, à la rédaction, à l&#x27;édition et à la publication d&#x27;articles de presse.</p><p>Un référentiel d&#x27;accompagnement est disponible <a href="https://github.com/justincastilla/elastic-newsroom/tree/main">ici</a>, et nous examinerons des exemples concrets d&#x27;A2A en action vers la fin de l&#x27;article, à la section 5.</p><h3>Produits requis</h3><p>Le <a href="https://github.com/justincastilla/elastic-newsroom/tree/main">référentiel</a> est constitué d&#x27;implémentations basées sur Python des agents A2A. Un serveur API est fourni en Flask, ainsi qu&#x27;un service de messagerie Python personnalisé appelé Event Hub, qui achemine les messages pour la journalisation et les mises à jour de l&#x27;interface utilisateur. Enfin, une interface utilisateur React est fournie pour une utilisation autonome des fonctionnalités de la salle de presse. Tout est contenu dans une image Docker pour faciliter la mise en œuvre. Si vous souhaitez utiliser les services directement sur votre machine, vous devez vous assurer que ces technologies sont installées :</p><p>Langages et moteurs d&#x27;exécution</p><ul><li>Python 13.12 - Langage de base du backend</li><li>Node.js 18+ - React UI en option</li></ul><p>Cadres de base et SDKS :</p><ul><li>A2A SDK 0.3.8 - Coordination et communication des agents</li><li>Anthropic SDK - Intégration de Claude pour la génération d&#x27;IA</li><li>Uvicorn - Serveur ASGI pour l&#x27;exécution des agents</li><li>FastMCP 2.12.5+ - Implémentation du serveur MCP</li><li>React 18.2 - Cadre d&#x27;interface utilisateur frontale</li></ul><p>Données &amp; recherche</p><ul><li>Elasticsearch 9.1.1+ - Indexation et recherche d&#x27;articles</li></ul><p>Déploiement de Docker (facultatif, mais recommandé)</p><ul><li>Docker 28.5.1+</li></ul><h2>Section 1 : Qu&#x27;est-ce que l&#x27;Agent2Agent (A2A) ?</h2><h3>Définition et concepts de base</h3><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p><strong>Spécification officielle</strong> <a href="https://a2a-protocol.org/latest/specification/">: https://a2a-protocol.org/latest/specification/</a></p><h3>Origines et évolution</h3><p>Le concept de communication Agent2Agent, ou de systèmes multi-agents, trouve ses racines dans les systèmes distribués, les microservices et la recherche multi-agents qui remonte à <a href="https://en.wikipedia.org/wiki/Multi-agent_system">plusieurs dizaines d&#x27;années</a>. Les premiers travaux sur l&#x27;intelligence artificielle distribuée ont jeté les bases d&#x27;agents capables de négocier, de coordonner et de collaborer. Ces premiers systèmes étaient destinés à des <a href="https://www.jasss.org/5/1/7.html">simulations sociales</a> à grande échelle, à la <a href="https://arxiv.org/html/2410.09403v1">recherche universitaire</a> et à la <a href="https://www.researchgate.net/publication/334765661_Generation_Expansion_Planning_Considering_Investment_Dynamic_of_Market_Participants_Using_Multi-agent_System">gestion des réseaux électriques</a>.</p><p>Avec l&#x27;arrivée des LLM et la réduction des coûts d&#x27;exploitation, les systèmes multi-agents sont devenus accessibles aux marchés &quot;grand public&quot;, avec le soutien de Google et de l&#x27;ensemble de la communauté des chercheurs en intelligence artificielle. Désormais connu sous le nom de systèmes Agent2Agent, l&#x27;ajout du protocole A2A a évolué pour devenir une norme moderne conçue spécifiquement pour l&#x27;ère des modèles linguistiques multiples et de grande envergure coordonnant les efforts et les tâches.</p><p>Le protocole A2A garantit une communication et une coordination transparentes entre les agents en appliquant des normes et des principes cohérents aux points d&#x27;interaction où les MFR se connectent et communiquent. Cette normalisation permet aux agents de différents développeurs - utilisant différents modèles sous-jacents - de travailler ensemble de manière efficace.</p><p>Les protocoles de communication ne sont pas nouveaux et sont largement ancrés dans presque toutes les transactions numériques effectuées sur l&#x27;internet. Si vous avez tapé <a href="https://www.elastic.co/search-labs">https://www.elastic.co/search-labs</a> dans un navigateur pour accéder à cet article, il y a de fortes chances que les protocoles TCP/IP, de transport HTTP et de recherche DNS aient tous été exécutés, ce qui nous garantit une expérience de navigation cohérente.</p><h3>Caractéristiques principales</h3><p>Les systèmes A2A reposent sur plusieurs principes fondamentaux qui garantissent une communication fluide. Le fait de s&#x27;appuyer sur ces principes garantit que différents agents, basés sur des LLM, des cadres et des langages de programmation différents, interagissent tous de manière transparente.</p><p>Voici les quatre grands principes :</p><ul><li><strong>Transmission de messages</strong>: Les agents communiquent par le biais de messages structurés dont les propriétés et les formats sont bien définis.</li><li><strong>Coordination</strong>: Les agents orchestrent des flux de travail complexes en se déléguant des tâches et en gérant les dépendances sans bloquer les autres agents.</li><li><strong>Spécialisation</strong>: Chaque agent se concentre sur un domaine ou une capacité spécifique, devenant ainsi un expert dans son domaine et offrant la possibilité d&#x27;accomplir des tâches basées sur cet ensemble de compétences.</li><li><strong>État distribué</strong>: L&#x27;état et les connaissances sont répartis entre les agents plutôt que centralisés, les agents ayant la possibilité de s&#x27;informer mutuellement de l&#x27;état d&#x27;avancement des tâches et des retours partiels (artefacts).</li></ul><h3>La salle de presse : Un exemple concret</h3><p>Imaginez une salle de rédaction numérique alimentée par des agents d&#x27;IA, chacun spécialisé dans un aspect différent du journalisme :</p><ul><li><strong>Chef de l&#x27;information</strong> (coordinateur/client) : Assigne les sujets et supervise le flux de travail</li><li><strong>Agent Reporter</strong>: Rédige des articles sur la base de recherches et d&#x27;interviews</li><li><strong>Agent de recherche</strong>: Recueille des faits, des statistiques et des informations de base</li><li><strong>Agent d&#x27;archivage</strong>: Recherche d&#x27;articles historiques et identification de tendances à l&#x27;aide d&#x27;Elasticsearch</li><li><strong>Agent rédacteur</strong>: Vérifie la qualité, le style et l&#x27;optimisation du référencement des articles.</li><li><strong>Agent de publication</strong>: Publie les articles approuvés sur la plateforme de blogs via CI/CD</li></ul><p>Ces agents ne travaillent pas isolément ; lorsque le chef de l&#x27;information confie un article sur l&#x27;<em>adoption des énergies renouvelables</em>, le journaliste a besoin du chercheur pour rassembler des statistiques, du rédacteur en chef pour réviser le projet et de l&#x27;éditeur pour publier l&#x27;article final. Cette coordination s&#x27;effectue par le biais de protocoles A2A.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cc0760dbd74c49b92fa00dafbb8c2e8740eb70b6-963x693.png" style="width: 100%;" /><h2>Section 2 : comprendre l&#x27;architecture A2A</h2><h3>Rôles de l&#x27;agent client et de l&#x27;agent distant</h3><p>Dans l&#x27;architecture A2A, les agents jouent deux rôles principaux. L&#x27;<strong>agent client</strong> est chargé de formuler et de communiquer des tâches aux autres agents du système. Il identifie les agents distants et leurs capacités, et utilise ces informations pour prendre des décisions éclairées en matière de délégation de tâches. L&#x27;agent client coordonne le flux de travail global, en veillant à ce que les tâches soient correctement réparties et à ce que le système progresse vers ses objectifs.</p><p>L&#x27;<strong>agent à distance</strong>, quant à lui, s&#x27;occupe des tâches déléguées par les clients. Il fournit des informations ou entreprend des actions spécifiques en réponse à des demandes, mais n&#x27;entreprend pas d&#x27;actions de manière indépendante. Les agents à distance peuvent également communiquer avec d&#x27;autres agents à distance si nécessaire pour s&#x27;acquitter des responsabilités qui leur sont confiées, créant ainsi un réseau collaboratif de capacités spécialisées.</p><p>Dans notre salle de presse, le chef de l&#x27;information joue le rôle d&#x27;agent client, tandis que le journaliste, le chercheur, le rédacteur en chef et l&#x27;éditeur sont des agents distants qui répondent aux demandes et se coordonnent les uns avec les autres.</p><h3>Capacités essentielles de l&#x27;A2A</h3><p>Les protocoles A2A définissent plusieurs capacités permettant une collaboration multi-agents :</p><h4>1. La découverte</h4><p>Les serveurs A2A doivent annoncer leurs capacités afin que les clients sachent quand et comment les utiliser pour des tâches spécifiques. Pour ce faire, les cartes d&#x27;agent sont des documents JSON qui décrivent les capacités, les entrées et les sorties d&#x27;un agent. Les cartes d&#x27;agent sont disponibles à des points d&#x27;extrémité cohérents et bien connus (tels que le point d&#x27;extrémité recommandé <code>/.well-known/agent-card.json</code> ), ce qui permet aux clients de découvrir et d&#x27;interroger les capacités d&#x27;un agent avant d&#x27;entamer une collaboration.</p><p>Voici un exemple de carte d&#x27;agent pour l&#x27;agent d&#x27;archivage personnalisé d&#x27;Elastic &quot;Archie Archivist&quot;. Notez que les fournisseurs de logiciels tels qu&#x27;Elastic hébergent leurs agents A2A et fournissent une adresse URL pour l&#x27;accès :</p><pre><code>{
  "name": "Archie Archivist",
  "description": "Helps find historical news documents in the Elasticsearch Index of archived news articles and content.",
  "url": "https://xxxxxxxxxxxxx-abc123.kb.us-central1.gcp.elastic.cloud/api/agent_builder/a2a/archive-agent",
  "provider": {
    "organization": "Elastic",
    "url": "https://elastic.co"
  },
  "version": "0.1.0",
  "protocolVersion": "0.3.0",
  "preferred_transport": "JSONRPC",
  "documentationURL": "https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server"
  "capabilities": {
    "streaming": false,
    "pushNotifications": false,
    "stateTransitionHistory": false
  },
  "skills": [
    {
      "id": "platform.core.search",
      "name": "platform.core.search",
      "description": "A powerful tool for searching and analyzing data within your Elasticsearch cluster.",
      "inputModes": ["text/plain", "application/json"],
      "outputModes": ["text/plain", "application/json"]
    },
    {
      "id": "platform.core.index_explorer",
      "name": "platform.core.index_explorer",
      "description": "List relevant indices, aliases and datastreams based on a natural language query.",
      "inputModes": ["text/plain", "application/json"],
      "outputModes": ["text/plain", "application/json"]
    }
  ],
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"]
}</code></pre><p>Cette carte d&#x27;agent révèle plusieurs aspects importants de l&#x27;agent d&#x27;archivage d&#x27;Elastic. L&#x27;agent s&#x27;identifie comme &quot;Archie Archivist&quot; et indique clairement son objectif : aider à trouver des documents d&#x27;actualités historiques dans un index Elasticsearch. La carte précise le fournisseur (Elastic) et la version du protocole (0.3.0), ce qui garantit la compatibilité avec d&#x27;autres agents conformes à la norme A2A. Plus important encore, le tableau <code>skills</code> énumère les capacités spécifiques offertes par cet agent, notamment une puissante fonctionnalité de recherche et une exploration intelligente de l&#x27;index. Chaque compétence définit les modes d&#x27;entrée et de sortie qu&#x27;elle prend en charge, ce qui permet aux clients de savoir exactement comment communiquer avec cet agent. Cet agent est dérivé du service Agent Builder d&#x27;Elastic, qui fournit une suite d&#x27;outils et de points d&#x27;extrémité d&#x27;API natifs soutenus par LLM pour avoir une conversation avec votre magasin de données, et pas seulement pour en extraire des données. L&#x27;accès aux agents A2A dans Elasticsearch peut être trouvé <a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server">ici.</a></p><h4>2. Négociation</h4><p>Les clients et les agents doivent se mettre d&#x27;accord sur les méthodes de communication - que les interactions se fassent par le biais de textes, de formulaires, d&#x27;iframes ou même d&#x27;audio/vidéo - afin de garantir une interaction correcte entre les utilisateurs et l&#x27;échange de données. Cette négociation a lieu au début de la collaboration des agents et établit les protocoles qui régiront leur interaction tout au long du flux de travail. Par exemple, un agent du service clientèle basé sur la voix peut négocier pour communiquer via des flux audio, tandis qu&#x27;un agent chargé de l&#x27;analyse des données peut préférer JSON structuré. Le processus de négociation permet aux deux parties d&#x27;échanger efficacement des informations dans un format adapté à leurs capacités et aux exigences de la tâche à accomplir.</p><p>Les capacités énumérées dans l&#x27;extrait JSON ci-dessus ont toutes des schémas d&#x27;entrée et de sortie ; ces schémas définissent la manière dont les autres agents doivent interagir avec cet agent.</p><h4>3. Gestion des tâches et des états</h4><p>Les clients et les agents ont besoin de mécanismes pour communiquer l&#x27;état des tâches, les changements et les dépendances tout au long de l&#x27;exécution des tâches. Il s&#x27;agit notamment de gérer l&#x27;ensemble du cycle de vie d&#x27;une tâche, depuis sa création et son affectation jusqu&#x27;aux mises à jour et aux changements d&#x27;état. Les statuts typiques sont les suivants : en attente, en cours, terminé ou en échec. Le système doit également suivre les dépendances entre les tâches afin de s&#x27;assurer que les travaux préalables sont achevés avant que les tâches dépendantes ne commencent. La gestion des erreurs et la logique de réessai sont également des éléments essentiels, qui permettent au système de se remettre gracieusement des défaillances et de continuer à progresser vers l&#x27;objectif principal.</p><p>Exemple de message de tâche :</p><pre><code>{
  "message_id": "msg_789xyz",
  "message_type": "task_request",
  "sender": "news_chief",
  "receiver": "researcher_agent",
  "timestamp": "2025-09-30T10:15:00Z",
  "payload": {
    "task_id": "task_456abc",
    "capability": "fact_gathering",
    "parameters": {
      "query": "renewable energy adoption rates in Europe 2024",
      "sources": ["eurostat", "iea", "ember"],
      "depth": "comprehensive"
    },
    "context": {
      "story_id": "story_123",
      "deadline": "2025-09-30T18:00:00Z",
      "priority": "high"
    }
  }
}</code></pre><p>Cet exemple de message de tâche démontre plusieurs aspects clés de la communication A2A.</p><ul><li>La structure du <strong>message</strong> comprend des métadonnées telles qu&#x27;un identifiant de message unique, le type de message envoyé, l&#x27;identification de l&#x27;expéditeur et du destinataire, et un horodatage pour le suivi et le débogage.</li><li>La <strong>charge utile</strong> contient les informations relatives à la tâche proprement dite, spécifiant la capacité invoquée sur l&#x27;agent distant et fournissant les paramètres nécessaires à l&#x27;exécution de cette capacité.</li><li>La section <strong>contexte</strong> fournit des informations supplémentaires qui aident l&#x27;agent récepteur à comprendre le flux de travail général, y compris les délais et les niveaux de priorité qui indiquent comment l&#x27;agent doit allouer ses ressources et planifier son travail.</li></ul><h4>4. La collaboration</h4><p>Les clients et les agents <strong>doivent</strong> permettre une interaction dynamique mais structurée, permettant aux agents de demander des clarifications, des informations ou des sous-actions au client, à d&#x27;autres agents ou à des utilisateurs. Cela crée un environnement de collaboration dans lequel les agents peuvent poser des questions complémentaires lorsque les instructions initiales sont ambiguës, demander un contexte supplémentaire pour prendre de meilleures décisions, déléguer des sous-tâches à d&#x27;autres agents ayant une expertise plus appropriée et fournir des résultats intermédiaires pour obtenir un retour d&#x27;information avant de procéder à l&#x27;ensemble de la tâche. Cette communication multidirectionnelle garantit que les agents ne travaillent pas de manière isolée, mais qu&#x27;ils sont au contraire engagés dans un dialogue permanent qui aboutit à de meilleurs résultats.</p><h3>Communication distribuée, d&#x27;égal à égal</h3><p>L&#x27;A2A permet une communication distribuée où les agents peuvent être hébergés par différentes organisations, certains agents étant maintenus en interne tandis que d&#x27;autres sont fournis par des services tiers. Ces agents peuvent fonctionner sur différentes infrastructures - couvrant potentiellement plusieurs fournisseurs de services en nuage ou des centres de données sur site. Ils peuvent utiliser différents LLM sous-jacents, certains agents étant alimentés par des modèles GPT, d&#x27;autres par Claude, et d&#x27;autres encore par des alternatives à code source ouvert. Les agents peuvent même opérer dans différentes régions géographiques pour se conformer aux exigences en matière de souveraineté des données ou pour réduire les temps de latence. Malgré cette diversité, tous les agents conviennent d&#x27;un protocole de communication commun pour l&#x27;échange d&#x27;informations, ce qui garantit l&#x27;interopérabilité indépendamment des détails de la mise en œuvre. Cette architecture distribuée offre une certaine souplesse dans la manière dont les systèmes sont construits et déployés, ce qui permet aux organisations de combiner les agents et les infrastructures les mieux adaptés à leurs besoins spécifiques.</p><p>Il s&#x27;agit de l&#x27;architecture finale de l&#x27;application de la salle de presse :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/82e01a0d9746038eafd69d11177042b5390507ae-1600x838.png" style="width: 100%;" /><h2>Section 3 : Protocole de contexte de modèle (PCM)</h2><h3>Définition et objectif</h3><p>Le Model Context Protocol (MCP) est un protocole standardisé développé par Anthropic pour améliorer et renforcer un LLM individuel avec des outils, des ressources et des invites définis par l&#x27;utilisateur, ainsi que d&#x27;autres ajouts supplémentaires à la base de code. MCP fournit une interface universelle entre les modèles linguistiques et les ressources externes dont ils ont besoin pour accomplir efficacement leurs tâches. Cet <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">article</a> présente l&#x27;état actuel du MCP avec des exemples de cas d&#x27;utilisation, les tendances émergentes et la propre mise en œuvre d&#x27;Elastic.</p><h3>Concepts de base du MCP</h3><p>MCP fonctionne selon une architecture client-serveur avec trois composants principaux :</p><ul><li><strong>Clients :</strong> applications (comme Claude Desktop ou des applications IA personnalisées) qui se connectent aux serveurs MCP pour accéder à leurs capacités.</li><li><strong>Serveurs</strong>: applications qui exposent les ressources, les outils et les messages-guides aux modèles linguistiques. Chaque serveur est spécialisé dans l&#x27;accès à des capacités ou à des sources de données spécifiques.<ul><li><strong>Outils</strong>: fonctions définies par l&#x27;utilisateur que les modèles peuvent invoquer pour effectuer des actions, telles que la recherche dans des bases de données, l&#x27;appel à des API externes ou l&#x27;exécution de transformations sur les données.</li><li><strong>Ressources :</strong> sources de données que les modèles peuvent lire, servies avec des données dynamiques ou statiques, et accessibles via des modèles d&#x27;URI (similaires aux routes REST).</li><li><strong>Invitations : </strong>modèles d&#x27;invitations réutilisables avec des variables qui guident le modèle dans l&#x27;accomplissement de tâches spécifiques.</li></ul></li></ul><h3>Modèle demande-réponse</h3><p>MCP suit un modèle d&#x27;interaction demande-réponse familier, similaire aux API REST. Le client (LLM) demande une ressource ou invoque un outil, puis le serveur MCP traite la demande et renvoie le résultat, que le LLM utilise pour poursuivre sa tâche. Ce modèle centralisé avec des serveurs périphériques offre un modèle d&#x27;intégration plus simple que la communication d&#x27;agent pair à pair.</p><h3>MCP dans la salle de presse</h3><p>Dans notre exemple de salle de presse, les agents individuels utilisent des serveurs MCP pour accéder aux outils et aux données dont ils ont besoin :</p><ul><li><strong>Le chercheur</strong> utilise l&#x27;agent:<ul><li>Serveur MCP News API (accès aux bases de données d&#x27;actualités)</li><li>Fact-Checking MCP Server (vérification des affirmations par rapport à des sources fiables)</li><li>Base de données académique MCP Server (articles et recherches universitaires)</li></ul></li><li>Utilisation de l&#x27;<strong>agent rapporteur</strong>:<ul><li>Guide de style MCP Server (normes de rédaction des salles de presse)</li><li>Serveur de modèles MCP (modèles et formats d&#x27;articles)</li><li>Bibliothèque d&#x27;images MCP Server (photos d&#x27;archives et graphiques)</li></ul></li><li>L<strong>&#x27;éditeur</strong> utilise l&#x27;agent:<ul><li>Grammar Checker MCP Server (outils de qualité linguistique)</li><li>Serveur MCP de détection du plagiat (vérification de l&#x27;originalité)</li><li>Analyse SEO MCP Server (optimisation des titres et des mots-clés)</li></ul></li><li>L&#x27;<strong>agent éditeur</strong> utilise :<ul><li>Serveur CMS MCP (système de gestion de contenu API)</li><li>Serveur CI/CD MCP (pipeline de déploiement)</li><li>Serveur Analytics MCP (suivi et contrôle)</li></ul></li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b67311e3b58b27f9eb1b42a7b1dbad47ef3be4ad-808x535.png" style="width: 100%;" /><h2><br/>Section 4 : comparaison des architectures</h2><h3>Quand utiliser A2A</h3><p>L&#x27;architecture A2A excelle dans les <strong>scénarios nécessitant une véritable collaboration multi-agents</strong>. Les flux de travail à plusieurs étapes nécessitant une coordination bénéficient grandement de l&#x27;A2A, en particulier lorsque les tâches impliquent plusieurs étapes séquentielles ou parallèles, les flux de travail nécessitant une itération et un affinement, et les processus avec des points de contrôle et des besoins de validation. Dans notre exemple de salle de presse, le flux de travail de l&#x27;article exige que le journaliste écrive, mais il peut être nécessaire de revenir au chercheur si la confiance en certains faits est faible, puis de passer au rédacteur en chef et enfin à l&#x27;éditeur.</p><p>La <strong>spécialisation spécifique à un domaine</strong> est un autre cas d&#x27;utilisation important de l&#x27;A2A. Lorsque plusieurs experts dans différents domaines sont nécessaires pour accomplir une tâche plus importante, chaque agent apportant une connaissance approfondie du domaine et des capacités de raisonnement spécialisées pour différents aspects, A2A fournit le cadre de coordination nécessaire pour établir ces connexions. La salle de rédaction en est un parfait exemple : le chercheur se spécialise dans la collecte d&#x27;informations, le journaliste dans la rédaction et le rédacteur en chef dans le contrôle de la qualité, chacun ayant une expertise distincte.</p><p>La nécessité d&#x27;un comportement autonome des agents rend l&#x27;A2A particulièrement utile. Les agents capables de<strong> prendre des décisions indépendantes, d&#x27;adopter un comportement proactif en fonction de l&#x27;évolution des conditions et de s&#x27;adapter de manière dynamique aux exigences du flux de travail</strong> s&#x27;épanouissent dans une architecture A2A. L&#x27;échelonnement horizontal des fonctions spécialisées est un autre avantage clé : plutôt que d&#x27;avoir un seul maître à tout faire, plusieurs agents spécialisés travaillent en coordination, et plusieurs instances du même agent peuvent gérer des tâches secondaires de manière asynchrone. Dans notre salle de presse, par exemple, lors d&#x27;une nouvelle de dernière minute, plusieurs agents de Reporter peuvent travailler simultanément sur différents aspects d&#x27;un même sujet.</p><p>Enfin, les tâches nécessitant une véritable collaboration multi-agents sont idéales pour l&#x27;A2A. Cela inclut les mécanismes d&#x27;<a href="https://arxiv.org/abs/2404.18796">évaluation du LLM en tant que jury</a>, les systèmes de consensus et de vote, et la <strong>résolution collaborative de problèmes où de multiples perspectives sont nécessaires</strong> pour atteindre le meilleur résultat.</p><h3>Quand utiliser MCP</h3><p>Le protocole de contexte de modèle est idéal pour étendre les capacités d&#x27;un modèle d&#x27;IA unique. Lorsqu&#x27;un modèle d&#x27;IA unique doit accéder à plusieurs outils et sources de données, MCP fournit la solution parfaite avec un raisonnement centralisé associé à des outils distribués et à une intégration simple des outils. Dans notre exemple de salle de presse, l&#x27;agent chercheur (un modèle) doit avoir accès à plusieurs sources de données, notamment l&#x27;API des actualités, les services de vérification des faits et les bases de données universitaires, toutes accessibles par l&#x27;intermédiaire de serveurs MCP normalisés.</p><p>L&#x27;intégration d&#x27;outils normalisés devient une priorité lorsque le partage et la réutilisation des intégrations d&#x27;outils sont importants. MCP se distingue ici par son écosystème de serveurs MCP préconstruits qui réduisent considérablement le temps de développement pour les intégrations courantes. Lorsque la simplicité et la facilité de maintenance sont requises, les modèles demande-réponse de MCP sont familiers aux développeurs, plus faciles à comprendre et à déboguer que les systèmes distribués, et leur complexité opérationnelle est moindre.</p><p>Enfin, le MCP est souvent proposé par les fournisseurs de logiciels pour faciliter la communication à distance avec leurs systèmes. Ces serveurs MCP proposés par les fournisseurs réduisent considérablement le temps d&#x27;intégration et de développement tout en offrant une interface standardisée avec les systèmes propriétaires, ce qui rend l&#x27;intégration beaucoup plus simple que le développement d&#x27;API personnalisées.</p><h3>Quand utiliser les deux (A2A ❤️&#x27;s MCP)</h3><p>De nombreux systèmes sophistiqués bénéficient de la combinaison d&#x27;A2A et de MCP, comme l&#x27;indique la <a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">documentation d&#x27;A2A sur l&#x27;intégration de MCP</a>. Les systèmes nécessitant à la fois une coordination et une normalisation sont des candidats idéaux pour une approche hybride. A2A s&#x27;occupe de la coordination des agents et de l&#x27;orchestration du flux de travail, tandis que MCP permet aux agents individuels d&#x27;accéder aux outils. Dans notre exemple de salle de presse, les agents se coordonnent via A2A, le flux de travail passant du journaliste au chercheur, puis au rédacteur en chef et à l&#x27;éditeur. Cependant, chaque agent utilise des serveurs MCP pour ses outils spécialisés, ce qui crée une séparation architecturale nette.</p><p>Plusieurs agents spécialisés, chacun utilisant MCP pour l&#x27;accès aux outils, représentent un modèle commun où il y a une couche de coordination des agents gérée par A2A et une couche d&#x27;accès aux outils gérée par MCP. Cette séparation claire des préoccupations rend les systèmes plus faciles à comprendre et à entretenir.</p><p>Les avantages de la combinaison de ces deux approches sont considérables. Vous bénéficiez des avantages organisationnels des systèmes multi-agents, notamment la spécialisation, l&#x27;autonomie et le traitement parallèle, tout en profitant de la normalisation et des avantages de l&#x27;écosystème du MCP, tels que l&#x27;intégration des outils et l&#x27;accès aux ressources. Il existe une séparation claire entre la coordination des agents (A2A) et l&#x27;accès aux ressources (MCP) et, surtout, l&#x27;A2A n&#x27;est pas nécessaire pour les petites tâches telles que l&#x27;accès à l&#x27;API uniquement - MCP les gère efficacement sans les frais généraux de l&#x27;orchestration multi-agents.</p><p><strong>FAQ : A2A vs. MCP - Cas d&#x27;utilisation</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Fonctionnalité</td><td style="border: 1px solid #ccc; padding: 4px;">Agent2Agent (A2A)</td><td style="border: 1px solid #ccc; padding: 4px;">Protocole de contexte de modèle (MCP)</td><td style="border: 1px solid #ccc; padding: 4px;">Hybride (A2A + MCP)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Objectif principal</td><td style="border: 1px solid #ccc; padding: 4px;">Coordination multi-agents : Permet à une équipe d'agents spécialisés de travailler ensemble sur des flux de travail complexes à plusieurs étapes.</td><td style="border: 1px solid #ccc; padding: 4px;">Amélioration de l'agent unique : Extension des capacités d'un seul LLM/Agent à l'aide d'outils, de ressources et de données externes.</td><td style="border: 1px solid #ccc; padding: 4px;">Une force combinée : A2A gère le flux de travail de l'équipe, tandis que MCP fournit des outils à chaque membre de l'équipe.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Exemple d'équipe de salle de presse</td><td style="border: 1px solid #ccc; padding: 4px;">La chaîne de travail : Chef de l'information → Reporter → Chercheur → Rédacteur en chef → Éditeur. Il s'agit de la couche de coordination.</td><td style="border: 1px solid #ccc; padding: 4px;">Outils individuels de l'agent : L'agent rapporteur accède au serveur de guides de style et au serveur de modèles (via MCP). Il s'agit de la couche d'accès aux outils.</td><td style="border: 1px solid #ccc; padding: 4px;">Le système complet : Le journaliste se coordonne avec le rédacteur en chef (A2A) et le journaliste utilise le serveur MCP de la bibliothèque d'images pour trouver un graphique pour l'article.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Quand utiliser quoi ?</td><td style="border: 1px solid #ccc; padding: 4px;">Lorsque vous avez besoin d'une véritable collaboration, d'une itération et d'un perfectionnement, ou d'une expertise spécialisée répartie entre plusieurs agents.</td><td style="border: 1px solid #ccc; padding: 4px;">Lorsqu'un agent unique a besoin d'accéder à plusieurs outils et sources de données ou nécessite une intégration standardisée avec des systèmes propriétaires.</td><td style="border: 1px solid #ccc; padding: 4px;">Lorsque vous avez besoin des avantages organisationnels des systèmes multi-agents et des avantages de normalisation et d'écosystème du MCP.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Prestations de base</td><td style="border: 1px solid #ccc; padding: 4px;">Autonomie et mise à l'échelle : Les agents peuvent prendre des décisions indépendantes et le système permet une mise à l'échelle horizontale des fonctions spécialisées.</td><td style="border: 1px solid #ccc; padding: 4px;">Simplicité et normalisation : Le raisonnement centralisé facilite le débogage et la maintenance et fournit une interface universelle pour les ressources.</td><td style="border: 1px solid #ccc; padding: 4px;">Séparation claire des préoccupations : Facilite la compréhension du système : A2A = travail d'équipe, MCP = accès aux outils.</td>
                    </tr>
              </tbody>
            </table>
          <img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ddf6a29b1107ac6a63e94ecef703abc561a29e1e-986x656.png" style="width: 100%;" /><h2>Conclusion</h2><p>Il s&#x27;agit de la première section de deux articles couvrant la mise en œuvre d&#x27;agents basés sur A2A et renforcés par des serveurs MCP pour fournir un support et un accès externe aux données et aux outils. La prochaine partie explorera le code réel pour démontrer qu&#x27;ils travaillent ensemble afin d&#x27;émuler les activités d&#x27;une salle de rédaction en ligne. Bien que les deux cadres soient extrêmement compétents et flexibles, vous verrez à quel point ils se complètent lorsqu&#x27;ils travaillent en tandem.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vous savez, pour le contexte - Partie I : L'évolution de la recherche hybride et de l'ingénierie contextuelle]]></title>
            <description><![CDATA[Découvrez comment la recherche hybride et l'ingénierie contextuelle ont évolué à partir de bases lexicales pour permettre la prochaine génération de flux de travail d'IA agentique.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/context-engineering-hybrid-search-evolution-agentic-ai</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/context-engineering-hybrid-search-evolution-agentic-ai</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Woody Walton]]></dc:creator>
            <pubDate>Wed, 12 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7e59a0671aa9ea32d68188a693936a66ebf48625-1000x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Notre tout nouveau monde d&#x27;IA agentique</h2><p>Comme beaucoup d&#x27;entre nous, je suis à la fois heureux et étonné du rythme auquel les capacités de l&#x27;IA évoluent. Les grands modèles de langage (LLM) et la recherche vectorielle nous ont d&#x27;abord lancés dans la révolution sémantique, où nous ne cherchions plus à trouver des choses à l&#x27;aide de mots-clés. Ensuite, les LLM nous ont montré de nouvelles façons d&#x27;interagir avec nos données, en utilisant des interfaces de chat pour transformer les demandes en langage naturel en réponses qui distillent de vastes bases de connaissances en résumés facilement consommables. Nous avons maintenant (déjà !) ont les prémices d&#x27;une logique automatisée pilotée par le LLM sous la forme de flux de travail &quot;d&#x27;IA agentique&quot; qui peuvent comprendre sémantiquement une demande entrante, raisonner sur les étapes à suivre, puis choisir parmi les outils disponibles pour exécuter itérativement des actions afin d&#x27;atteindre ces objectifs.</p><p>La promesse de l&#x27;IA agentique nous oblige à évoluer et à ne plus utiliser principalement l&#x27;&quot;ingénierie de l&#x27;invite&quot; pour façonner nos interactions génératives avec l&#x27;IA, mais à nous concentrer sur la manière dont nous pouvons aider les outils agentiques à obtenir les informations supplémentaires les plus pertinentes et les plus efficaces que le LLM doit prendre en compte lorsqu&#x27;il génère ses réponses - l&#x27;&quot;ingénierie du contexte&quot; est la prochaine frontière. La recherche hybride est de loin le moyen le plus puissant et le plus souple de faire apparaître un contexte pertinent, et la plateforme Search AI d&#x27;Elastic ouvre une toute nouvelle voie pour exploiter les données au service de l&#x27;ingénierie contextuelle. Dans cet article, nous allons examiner comment les LLM ont changé le monde de la recherche d&#x27;informations sous deux angles, puis comment ils peuvent travailler ensemble pour obtenir de meilleurs résultats. Il y a beaucoup de chemin à parcourir...</p><h2>Partie I : Comment les LLM ont changé la recherche</h2><p>Commençons par la façon dont les LLM ont changé la façon dont nous accédons à l&#x27;information et dont nous la recherchons.</p><h3>Notre héritage lexical</h3><p>Nous vivons tous depuis longtemps dans le monde quelque peu limité de la recherche lexicale (plutôt bien, du mieux que nous pouvons). La recherche est le premier outil que nous utilisons lorsque nous faisons des recherches ou que nous commençons un nouveau projet. Jusqu&#x27;à récemment, il nous incombait de formuler nos requêtes de manière à ce qu&#x27;elles soient comprises par un moteur de recherche lexical. La recherche lexicale repose sur la mise en correspondance d&#x27;une certaine forme de termes d&#x27;interrogation avec des mots-clés trouvés dans un corpus de documents, que le contenu soit structuré ou non. Pour qu&#x27;une recherche lexicale aboutisse à un document, celui-ci doit correspondre à ce mot-clé (ou disposer d&#x27;un vocabulaire contrôlé tel qu&#x27;une liste de synonymes ou un dictionnaire pour établir le lien conceptuel).</p><pre><code>POST my-index/_search
{
  "size": 10,
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
}</code></pre><p><em>Exemple de  requête<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">lexicale multi-correspondance</a></em></p><p>Au moins, les moteurs de recherche ont la possibilité de renvoyer les résultats avec un score de pertinence. Les moteurs de recherche offrent une multitude d&#x27;options syntaxiques pour cibler efficacement les données indexées et des algorithmes de pertinence intégrés qui évaluent les résultats en fonction de l&#x27;intention de la syntaxe de la requête de l&#x27;utilisateur. Les moteurs de recherche bénéficient de décennies de progrès dans les algorithmes de classement par pertinence, ce qui en fait une plate-forme efficace de recherche de données capable de fournir des résultats notés et triés en fonction de leur pertinence par rapport à la requête. Les bases de données et autres systèmes qui utilisent SQL comme principale méthode de recherche de données sont ici désavantagés : il n&#x27;y a pas de concept de pertinence dans une requête de base de données ; le mieux qu&#x27;ils puissent faire est de trier les résultats par ordre alphabétique ou numérique. La bonne nouvelle, c&#x27;est que vous obtiendrez tous les résultats (rappel) avec ces mots-clés, mais qu&#x27;ils ne seront pas nécessairement dans un ordre utile par rapport à la <em>raison pour laquelle</em> vous les avez demandés (précision). C&#x27;est un point important, comme nous le verrons bientôt...</p><h3>Entrez dans le dragon (sémantique)</h3><p>Le potentiel des représentations vectorielles de l&#x27;information en tant qu&#x27;alternative à la recherche par mot-clé fait l&#x27;objet de recherches depuis <a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">longtemps</a>. Les vecteurs sont très prometteurs parce qu&#x27;ils nous permettent de sortir du mode de correspondance par mot-clé uniquement - parce qu&#x27;ils sont des représentations numériques des termes et des poids, les vecteurs permettent de rapprocher mathématiquement les concepts sur la base de la compréhension par un modèle linguistique de la manière dont les termes sont liés les uns aux autres dans le domaine d&#x27;apprentissage. Le retard pris par la recherche vectorielle générale s&#x27;explique par le fait que les modèles étaient essentiellement limités à des domaines spécifiques et qu&#x27;ils n&#x27;étaient tout simplement pas assez vastes pour comprendre suffisamment les nombreux concepts différents qu&#x27;un terme peut représenter dans des contextes différents.</p><p>Ce n&#x27;est que lorsque les grands modèles de langage (LLM) sont apparus il y a quelques années, avec leur capacité à s&#x27;entraîner sur des quantités de données beaucoup plus importantes (en utilisant des <a href="https://en.wikipedia.org/wiki/Transformer_(deep_learning_architecture)">transformateurs</a> et de l&#x27;<a href="https://en.wikipedia.org/wiki/Attention_(machine_learning)">attention</a>), que la recherche vectorielle est devenue pratique - la taille et la profondeur des LLM ont finalement permis aux vecteurs de stocker suffisamment de nuances pour qu&#x27;ils puissent réellement capturer le sens sémantique. Cette augmentation soudaine de la profondeur de compréhension a permis aux LLM de remplir un grand nombre de fonctions de traitement du langage naturel (NLP) qui étaient auparavant verrouillées, la plus importante étant peut-être la capacité à déduire le terme suivant le plus probable dans une séquence, compte tenu du contexte de ce qui se trouve dans la séquence jusqu&#x27;à présent. L&#x27;inférence est le processus qui donne à l&#x27;IA générative sa capacité quasi humaine à produire du texte. Le texte généré par l&#x27;IA s&#x27;appuie sur la compréhension qu&#x27;a le LLM de la manière dont les termes sont liés dans ses données d&#x27;apprentissage et utilise également la formulation de la demande pour désambiguïser les différents contextes dans lesquels les termes peuvent apparaître.</p><p>Aussi magique que soit l&#x27;IA générative, les LLM présentent <em>des</em> limites qui entraînent des erreurs de qualité et de précision, communément appelées hallucinations. Les hallucinations se produisent lorsque le LLM n&#x27;a pas accès aux informations (ou n&#x27;est pas guidé vers le bon contexte) pour fonder sa réponse sur la vérité et que, pour être utile, il génère une réponse confiante et plausible qui a été inventée. Cela s&#x27;explique en partie par le fait que les LLM apprennent l&#x27;usage de la langue dans de vastes domaines d&#x27;informations diverses, mais qu&#x27;ils doivent cesser leur formation à un moment donné, de sorte que leur compréhension est soumise à un facteur temporel, ce qui signifie que le modèle ne peut savoir que ce qui était exact jusqu&#x27;au moment où il a cessé de se former. Un autre facteur d&#x27;hallucinations est que le modèle ne connaît généralement pas les données privées (données non disponibles sur l&#x27;internet public), ce qui est particulièrement important lorsque ces données contiennent des termes et une nomenclature spécifiques.</p><h3>Bases de données vectorielles</h3><p>Les LLM vectorisent le contenu dans l&#x27;espace de leur modèle à l&#x27;aide d&#x27;une technique appelée &quot;text embedding&quot;, qui consiste à <a href="https://www.elastic.co/search-labs/blog/hybrid-search-multiple-embeddings">intégrer</a> ou à cartographier la signification sémantique du contenu dans la vision du monde du modèle sur la base de la formation qu&#x27;il a reçue. Quelques étapes sont nécessaires pour préparer et traiter le contenu à intégrer, notamment le <a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">découpage en morceaux</a> et la tokenisation (et la <a href="https://www.kaggle.com/code/danishmahdi/subword-tokenization-bpe-wordpiece-and-unigram">tokenisation des sous-mots</a>). Le résultat est généralement un ensemble de vecteurs denses représentant la compréhension par le modèle de la signification de ce morceau de contenu dans son espace vectoriel. Le découpage est un processus inexact qui vise à adapter le contenu aux limites des contraintes de traitement d&#x27;un modèle pour générer des encastrements, tout en essayant de regrouper le texte apparenté dans un morceau à l&#x27;aide de constructions sémantiques telles que les indicateurs de phrase et de paragraphe.</p><p>La nécessité d&#x27;un découpage en morceaux peut entraîner une certaine perte sémantique dans un document incorporé, car les morceaux individuels ne sont pas entièrement associés à d&#x27;autres morceaux du même document. L&#x27;opacité inhérente aux réseaux neuronaux peut aggraver cette perte - un LLM est véritablement une &quot;boîte noire&quot; dans laquelle les connexions entre les termes et les concepts établies au cours de la formation sont non déterministes et ne peuvent être interprétées par les humains. Cela pose des problèmes d&#x27;explicabilité, de reproductibilité, de partialité inconsciente et, potentiellement, de perte de confiance et d&#x27;exactitude. Néanmoins, la possibilité de relier sémantiquement des idées, de ne pas être lié à des mots-clés spécifiques lors de la recherche, est extrêmement puissante :</p><pre><code>POST my-index/_search 
{
  "size": 10, 
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
} </code></pre><p><em>Un exemple <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">de</a> requête sémantique</em></p><p>Les bases de données vectorielles ne sont pas des moteurs de recherche, mais des bases de données ! Lorsqu&#x27;une <a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">recherche de similarité vectorielle</a> est effectuée, les termes de la requête sont encodés pour trouver un ensemble de coordonnées (d&#x27;intégration) dans l&#x27;espace vectoriel du modèle. Ces coordonnées sont ensuite utilisées comme œil-de-bœuf pour trouver les documents qui sont les &quot;plus proches voisins&quot; de l&#x27;œil-de-bœuf - ce qui signifie que le rang d&#x27;un document (ou sa place dans les résultats) est déterminé par la <em>distance de</em> similarité calculée entre les coordonnées de ce document et les coordonnées de la requête. Dans quel sens le classement doit-il primer, lequel des contextes possibles est le plus proche de l&#x27;intention de l&#x27;utilisateur ? L&#x27;image à laquelle je me réfère est une scène du film <a href="https://www.youtube.com/watch?v=x3h7xz558EY&amp;start=3&amp;end=86">Stargate</a>, où nous avons les six points de coordonnées qui se croisent pour nous indiquer la destination (l&#x27;œil-de-bœuf), mais nous ne pouvons pas nous y rendre sans connaître le &quot;7e symbole&quot; - les coordonnées du point de départ représentant l&#x27;intention subjective de l&#x27;utilisateur. Ainsi, au lieu que le classement relatif des vecteurs soit basé sur une sphère de similarité toujours plus étendue et indifférenciée, en tenant compte de l&#x27;intention subjective de la requête par le biais d&#x27;une syntaxe expressive et d&#x27;une notation de la pertinence, nous pouvons obtenir quelque chose qui ressemble à un <em>cylindre</em> de pertinence subjective graduée.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ddfaae0c1496d08d7d30419e6d2aeaeacfc0ea2-1600x544.png" style="width: 100%;" /><p>Les capacités d&#x27;inférence d&#x27;un LLM peuvent aider à identifier le contexte le plus probable <em>pour</em> la requête, mais le problème est que <em>sans aide, les</em> coordonnées de la requête entrante <em>ne</em> peuvent être déterminées que par la façon dont le modèle a été formé à l&#x27;origine.</p><p>D&#x27;une certaine manière, on pourrait dire que la similarité vectorielle va à l&#x27;extrême opposé d&#x27;une correspondance stricte par mot-clé - sa force réside dans sa capacité à surmonter les problèmes d&#x27;inadéquation des termes, mais <a href="https://medium.com/data-science/vector-embeddings-are-lossy-heres-what-to-do-about-it-4f9a8ee58bb7">presque jusqu&#x27;à la faute</a>: Les LLM tendent à unifier des concepts apparentés plutôt qu&#x27;à les différencier. La similarité vectorielle améliore notre capacité à faire correspondre le contenu sur le plan sémantique, mais ne garantit pas la précision car elle peut négliger des mots-clés exacts et des détails spécifiques qui ne sont pas suffisamment désambiguïsés par le modèle. La recherche de similarités vectorielles est puissante en soi, mais nous avons besoin de moyens pour corréler les résultats que nous extrayons d&#x27;une base de données vectorielle avec les résultats d&#x27;autres méthodes d&#x27;extraction.</p><h3>Techniques de repositionnement</h3><p>C&#x27;est le moment de mentionner une technique générale appelée &quot;reranking&quot;, qui consiste à réévaluer ou à normaliser les ensembles de résultats en fonction d&#x27;un ordre de classement unifié. Le besoin de reclassement peut être dû au fait que les résultats provenant de sources multiples ou de méthodes de recherche ont des mécanismes de classement/évaluation différents (ou aucun, SQL !), ou le reclassement peut être utilisé pour aligner sémantiquement les résultats provenant de sources non sémantiques sur la requête de l&#x27;utilisateur. Le reclassement est une opération de deuxième étape, c&#x27;est-à-dire un ensemble de résultats qui ont été collectés par une méthode de <em>recherche initiale</em> (c&#x27;est-à-dire le SQL, recherche lexicale, recherche vectorielle) sont ensuite réordonnées avec une méthode de notation différente.</p><p>Plusieurs approches sont disponibles, notamment <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr">Learning-To-Rank (LTR)</a> et <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">Reciprocal Rank Fusion (RRF)</a> - LTR est utile pour capturer les caractéristiques des résultats de recherche (likes, évaluations, clics, etc.) et les utiliser pour noter et améliorer ou biaiser les résultats. RRF est parfait pour fusionner les résultats obtenus à partir de différentes modalités d&#x27;interrogation (par ex. les recherches dans les bases de données lexicales et vectorielles) en une seule liste de résultats. Elastic offre également la possibilité d&#x27;ajuster les scores à l&#x27;aide de méthodes de <a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">reclassement linéaire</a>.</p><p>L&#x27;une des techniques de reclassement les plus efficaces est cependant le <a href="https://www.elastic.co/docs/solutions/search/ranking/semantic-reranking">reclassement sémantique</a>, qui utilise la compréhension sémantique d&#x27;un LLM pour analyser les vecteurs d&#x27;intégration de la requête et des résultats, puis appliquer la notation de la pertinence/le reclassement pour déterminer l&#x27;ordre final. Le reranking sémantique nécessite une connexion à un modèle de reranking, bien sûr, et Elasticsearch fournit une <a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">API d&#x27;inférence</a> qui vous permet de créer des points d&#x27;extrémité de <strong>rerank</strong> qui exploitent des modèles intégrés<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-rerank">(Elastic Rerank</a>), des modèles tiers <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning">importés</a> ou des services hébergés en externe tels que <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-cohere">Cohere</a> ou <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-googlevertexai">Google Vertex AI.</a> Vous pouvez ensuite effectuer un reclassement grâce à la syntaxe d&#x27;abstraction de la requête du <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">récupérateur</a>:</p><pre><code>POST my-index/_search 
{
  "size": 10,
  "retriever": {
    "text_similarity_reranker": {
      "retriever": {
        "rrf": {
          "retrievers": [
            {
              "standard": {
                "query": {
                  "multi_match": {
                    "query": "machine learning applications",
                    "fields": ["title", "content"]
                  }
                }
              }
            },
            {
              "knn": {
                "field": "semantic-content-field",
                "k": 10,
                "num_candidates": 100,
                "query_vector_builder": {
                  "text_embedding": {
                    "model_id": "my-text-embedding-model",
                    "model_text": "machine learning applications"
                  }
                }
              }
            }
          ],
          "rank_window_size": 50,
          "rank_constant": 20
        }
      }
    },
    "field": "content",
    "inference_id": "my-reranker",
    "inference_text": "machine learning applications",
    "rank_window_size": 20
  }
}</code></pre><p><em>Exemple d&#x27;opération de remise en ordre d&#x27;un récupérateur en plusieurs étapes</em></p><p>Ça a l&#x27;air bien, non ? Nous pouvons effectuer un reclassement sur des résultats provenant de sources disparates et nous rapprocher d&#x27;une compréhension sémantique de tous les types de contenu... Le reclassement sémantique peut être coûteux tant sur le plan du calcul que du temps de traitement nécessaire, et pour cette raison, le reclassement sémantique ne peut être effectué que sur un nombre limité de résultats, ce qui signifie que la <em>manière dont</em> ces résultats initiaux sont récupérés est importante.</p><h3>La méthode de recherche contextuelle est importante</h3><p>L&#x27;intention subjective est un facteur important dans la détermination de l&#x27;exactitude d&#x27;un résultat, dans l&#x27;évaluation de sa pertinence. Sans la possibilité de prendre en compte l&#x27;intention de l&#x27;utilisateur lors de l&#x27;exécution de la requête (telle qu&#x27;elle est exprimée par une syntaxe flexible ou par un reclassement de deuxième niveau), nous ne pouvons que sélectionner les contextes existants déjà encodés dans l&#x27;espace de modélisation. La façon dont nous abordons généralement ce manque de contexte est par le biais de techniques telles que <a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation">Retrieval Augment Generation (RAG).</a> La méthode RAG consiste à déplacer les coordonnées de la requête en incluant des termes connexes supplémentaires issus d&#x27;une pré-requête de données contextuelles pertinentes. Le moteur qui fournit ce contexte supplémentaire et <em>sa</em> méthode initiale de recherche sont donc d&#x27;autant plus importants pour la précision du contexte !</p><p>Passons en revue les différentes méthodes de recherche contextuelle et la manière dont elles peuvent aider ou nuire à une opération RAG :</p><ul><li><strong>La recherche hybride sans moteur de recherche manque encore de pertinence subjective.</strong> Si la plateforme qui fournit le RAG est principalement basée sur SQL (ce qui inclut la plupart des plateformes de &quot;lac de données&quot;), elle ne dispose pas d&#x27;un système de notation de la pertinence au stade de la recherche initiale. De nombreuses plateformes de lac de données proposent leur propre version de la recherche hybride (et non de la recherche), combinant généralement des techniques de reranking telles que le reranking sémantique et le RRF sur leur recherche basée sur SQL et les résultats de la base de données vectorielles. Un simple tri est manifestement insuffisant pour un classement subjectif, mais même lorsqu&#x27;il est utilisé comme base pour une opération de reclassement sémantique à la deuxième étape, SQL comme la recherche à la première étape devient un problème lorsque le reclassement sémantique n&#x27;est effectué que sur les &quot;k premiers&quot; résultats - sans un moyen de noter les résultats à la recherche, quelle garantie avons-nous que les <em>meilleurs</em> résultats se trouvent effectivement dans les premiers résultats ?</li><li><strong>La similarité vectorielle n&#x27;est pas suffisante pour le RAG</strong>. Il s&#x27;agit en fait d&#x27;un ensemble de problèmes combinés - la perte de l&#x27;intégration, les méthodes naïves de regroupement, le mode de calcul de la similarité et la composante manquante cruciale de l&#x27;intention subjective. L&#x27;un des principaux objectifs de RAG est de fonder les interactions génératives de l&#x27;IA sur la vérité objective, à la fois pour éviter les hallucinations et pour informer le LLM des informations privées dont il n&#x27;a pas eu connaissance au cours de la formation. Nous pouvons utiliser le contexte supplémentaire fourni par le RAG pour contraindre et orienter les MFR à prendre en compte les liens et les détails que nous savons être les plus importants pour répondre à la question posée. Pour ce faire, nous devons utiliser des approches sémantiques et <em>lexicales</em>.</li><li><strong>RAG (grep/regex) basé sur des fichiers.</strong> Certains <a href="https://www.nicolasbustamante.com/p/the-rag-obituary-killed-by-agents">secteurs</a> de l&#x27;univers de l&#x27;IA agentique préconisent l&#x27;utilisation de fenêtres contextuelles considérablement agrandies qui accèdent aux fichiers locaux via grep et regex pour RAG plutôt que des plates-formes de recherche externes. L&#x27;idée est qu&#x27;en disposant d&#x27;une fenêtre contextuelle beaucoup plus large, les LLM seront en mesure d&#x27;établir des connexions conceptuelles au sein de leur propre espace de réflexion plutôt que de s&#x27;appuyer sur des éléments fragmentés et de multiples méthodes/plateformes de recherche pour collecter des informations pertinentes. S&#x27;il est vrai en théorie que le fait de disposer d&#x27;un document entier donne une image plus complète que des segments de document, cela ne peut fonctionner que dans des domaines de données restreints (ou, par exemple, lors de la fourniture de fichiers pour le <a href="https://en.wikipedia.org/wiki/Vibe_coding">vibecodage</a>), et même dans ce cas, la méthode de recherche initiale est un balayage de tous les documents avec une correspondance par mot-clé uniquement.</li></ul><p><strong>La recherche, c&#x27;est plus que l&#x27;extraction</strong></p><p>Les moteurs de recherche sont conçus pour rendre les requêtes aussi rapides et flexibles que possible. En interne, ils utilisent des structures de données spécialisées pour stocker et récupérer différents types de données de manière adaptée à ces types de données. Elasticsearch permet d&#x27;optimiser le stockage et l&#x27;interrogation de pratiquement tous les types de données, y compris la recherche lexicale non structurée/texte intégral (correspondance, phrase, proximité, correspondance multiple), la correspondance et le filtrage rapides par mot-clé (correspondance exacte), les plages numériques, les dates, les adresses IP, et est très flexible dans la manière dont il stocke les structures de documents (par ex. les documents imbriqués ou aplatis). Elasticsearch est également une base de données vectorielle native capable de stocker et d&#x27;interroger des types de vecteurs épars et denses, et nous continuons à explorer des moyens innovants (par exemple, <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization (BBQ)</a> &amp; <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>) pour maintenir la fidélité de la recherche tout en améliorant la vitesse, l&#x27;évolutivité et les coûts associés au contenu vectorisé. La plateforme Elasticsearch offre également une résilience des données et une haute disponibilité intégrées, ainsi que des fonctionnalités de gestion du cycle de vie des données, telles que les <a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">instantanés consult</a> ables, qui vous permettent de conserver les données rarement consultées ou les données conservées à long terme sur un stockage objet rentable, tout en conservant une capacité de recherche totale.</p><h3>La recherche hybride, c&#x27;est le meilleur des mondes</h3><p><a href="https://www.elastic.co/what-is/hybrid-search">Recherche hybride</a> (et pas seulement recherche hybride !) combine les forces de la recherche lexicale traditionnelle avec la compréhension sémantique des LLM et la recherche par similarité vectorielle. Cette synergie permet de cibler des résultats très pertinents au stade de la <em>recherche</em> grâce à l&#x27;une des options syntaxiques souples proposées par un moteur de recherche : options syntaxiques axées sur l&#x27;intention et évaluation de la pertinence, recherche de données multimodales, filtrage, agrégations et biais. Avec une syntaxe de recherche telle que <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> et des <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">extracteurs</a> à plusieurs niveaux, nous pouvons combiner de manière flexible la recherche traditionnelle avec la recherche sémantique, les filtres et plusieurs techniques de reclassement en une seule requête.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f25180cb430414b99ae553d3b8eb161dbccea4d4-1920x1080.png" style="width: 100%;" /><p>L&#x27;un des principaux avantages de la recherche hybride est que vos requêtes peuvent utiliser une syntaxe spécialisée pour plusieurs types de données simultanément. Ces différentes syntaxes d&#x27;interrogation peuvent être utilisées non seulement pour <em>trouver des</em> résultats, mais aussi comme filtres ou agrégations <em>sur les</em> résultats. Par exemple, l&#x27;<a href="https://www.elastic.co/docs/explore-analyze/geospatial-analysis">analyse géospatiale</a> est l&#x27;un des types d&#x27;interrogation les plus courants qui est fréquemment combiné à d&#x27;autres syntaxes. Vous pouvez par exemple demander des résultats dont les coordonnées géographiques se situent à une distance donnée d&#x27;un point, ou demander des agrégations de vos résultats par région, ou encore des agrégations pour suivre et alerter sur les mouvements à l&#x27;intérieur ou à l&#x27;extérieur d&#x27;une zone. Avec la recherche hybride, vous avez la possibilité de combiner des syntaxes pour cibler les résultats de la manière la plus précise possible, afin de retrouver le contenu le plus proche de votre contexte.</p><h2>Intermède</h2><p>Cette première partie raconte comment la recherche vectorielle a changé la façon dont nous pouvons récupérer des données et prépare le terrain pour les changements que les LLM ont apportés aux mécanismes d&#x27;interrogation que nous utilisons pour interagir avec les données. Nous allons faire comme si nous avions dû diviser ce texte en plusieurs parties pour que les LLM puissent le comprendre sans perdre le contexte... ;-) Nous en apprendrons plus sur les <em>raisons de cette importance</em> dans la <a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">Partie II : L&#x27;IA agentique et le besoin d&#x27;ingénierie contextuelle</a>, et dans la Partie III, nous reviendrons à notre discussion sur la recherche hybride.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Configurer le découpage récursif pour les documents structurés dans Elasticsearch]]></title>
            <description><![CDATA[Apprenez à configurer le découpage récursif dans Elasticsearch avec la taille des morceaux, les groupes de séparateurs et les listes de séparateurs personnalisées pour une indexation optimale des documents structurés.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/recursive-chunking-structured-documents-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/recursive-chunking-structured-documents-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Daniel Rubinstein]]></dc:creator>
            <pubDate>Tue, 11 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/591872e31880768ca927507654a621addc0d124d-1600x960.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Depuis la version 8.16, les utilisateurs peuvent configurer la stratégie de découpage utilisée lors de l&#x27;ingestion de longs documents dans des champs de texte sémantique. Depuis la version 9.1 / 8.19, nous avons introduit une nouvelle stratégie de découpage récursif configurable qui utilise une liste d&#x27;expressions régulières pour découper le document. L&#x27;objectif du découpage en morceaux est de diviser un long document en sections qui encapsulent un contenu apparenté. Nos stratégies existantes permettent de diviser le texte selon une granularité de mots/phrases, mais les documents écrits dans des formats structurés (ex. Markdown) contiennent souvent des contenus connexes dans des sections définies par des chaînes de séparation (ex. ). Pour ces types de documents, nous introduisons la stratégie de découpage récursif afin d&#x27;exploiter le format des documents structurés pour créer de meilleurs morceaux !</p><h2>Qu&#x27;est-ce que le découpage récursif ?</h2><p>Le découpage récursif parcourt une liste de sections fournies en séparant les modèles afin de diviser progressivement un document en segments plus petits jusqu&#x27;à ce qu&#x27;ils atteignent une taille maximale souhaitée.</p><h3>Comment configurer le découpage récursif ?</h3><p>Les valeurs configurables fournies par l&#x27;utilisateur pour le découpage récursif sont les suivantes :</p><ul><li>(obligatoire) <code>max_chunk_size</code>: Le nombre maximum de mots dans un bloc.</li><li>L&#x27;un ou l&#x27;autre :<ul><li><code>separators</code>: Une liste de motifs de chaînes regex qui seront utilisés pour découper le document en morceaux.</li><li><code>separator_group</code>: Une chaîne qui correspondra à une liste par défaut de séparateurs définis par Elastic à utiliser pour des types de documents spécifiques. Actuellement, <code>markdown</code> et <code>plaintext</code> sont disponibles.</li></ul></li></ul><h3>Comment fonctionne le découpage récursif ?</h3><p>Le processus de découpage récursif d&#x27;un document d&#x27;entrée, d&#x27;un <code>max_chunk_size</code> (mesuré en mots) et d&#x27;une liste de chaînes de séparation est le suivant :</p><ol><li>Si le document d&#x27;entrée est déjà compris dans la taille maximale des morceaux, il renvoie un seul morceau couvrant l&#x27;ensemble du document d&#x27;entrée.</li><li>Découper le texte en morceaux potentiels sur la base des occurrences du séparateur. Pour chaque morceau potentiel :<ol><li>Si le morceau potentiel ne dépasse pas la taille maximale, il est ajouté à la liste des morceaux à renvoyer à l&#x27;utilisateur.</li><li>Sinon, répétez l&#x27;étape 2, en utilisant uniquement le texte du morceau potentiel et en le séparant à l&#x27;aide du séparateur suivant dans la liste. S&#x27;il n&#x27;y a plus de séparateurs à essayer, il faut se rabattre sur le découpage en phrases.</li></ol></li></ol><h2>Exemples de configuration du découpage récursif</h2><p>Outre la taille des morceaux, la principale configuration du découpage récursif consiste à sélectionner les séparateurs à utiliser pour diviser vos documents. Si vous ne savez pas par où commencer, Elasticsearch propose quelques groupes de séparateurs par défaut qui peuvent être utilisés pour des cas d&#x27;utilisation courants.</p><h3>Utilisation de groupes de séparation</h3><p>Pour utiliser un groupe séparateur, il suffit d&#x27;indiquer le nom du groupe que vous souhaitez utiliser lors de la configuration des paramètres de regroupement. Par exemple :</p><pre><code>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separator_group": "plaintext"
}</code></pre><p>Vous obtiendrez ainsi une stratégie de découpage récursif qui utilise la liste de séparateurs <code>[&quot;(?&lt;!\\n)\\n\\n(?!\\n)&quot;, &quot;(?&lt;!\\n)\\n(?!\\n)&quot;)]</code>. Cela fonctionne bien pour les applications génériques de texte brut, en séparant deux caractères de retour à la ligne, suivis d&#x27;un caractère de retour à la ligne.</p><p>Nous proposons également un groupe de séparateurs <code>markdown</code> qui utilisera la liste des séparateurs :</p><pre><code>[
"\n# ",
       "\n## ",
       "\n### ",
       "\n#### ",
       "\n##### ",
       "\n###### ",
       "\n^(?!\\s*$).*\\n-{1,}\\n",
       "\n^(?!\\s*$).*\\n={1,}\\n"
]</code></pre><p>Cette liste de séparateurs fonctionnera bien pour les cas d&#x27;utilisation généraux de markdown, en séparant chacun des 6 niveaux d&#x27;en-tête et les caractères de coupure de section.</p><p>Lors de la création d&#x27;une ressource (point d&#x27;inférence/champ textuel sémantique), la liste des séparateurs correspondant au groupe de séparateurs du moment sera stockée dans vos configurations. Si le groupe de séparateurs est mis à jour ultérieurement, cela ne modifiera pas le comportement des ressources déjà créées.</p><h3>Utilisation d&#x27;une liste de séparateurs personnalisée</h3><p>Si l&#x27;un des groupes de séparateurs prédéfinis ne convient pas à votre cas d&#x27;utilisation, vous pouvez définir une liste personnalisée de séparateurs répondant à vos besoins. Notez que des expressions régulières peuvent être fournies dans la liste des séparateurs. Voici un exemple de paramètres de regroupement configurés avec des séparateurs personnalisés :</p><pre><code>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separators": ["\n\n", "\n", "<my-custom-separator>"]
}</code></pre><p>La stratégie de découpage en morceaux découpera 2 caractères de nouvelle ligne, suivis d&#x27;un caractère de nouvelle ligne, et enfin une chaîne de caractères <code>“&lt;my-custom-separator&gt;”</code>.</p><h2>Un exemple de découpage récursif en action</h2><p>Voyons un exemple de découpage récursif en action. Pour cet exemple, nous utiliserons les paramètres de découpage suivants avec une liste personnalisée de séparateurs qui découpent un document markdown en utilisant les deux premiers niveaux d&#x27;en-tête :</p><pre><code>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separators": ["\n# ", "\n## "]
}</code></pre><p>Examinons un simple document Markdown non tronqué :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a5507f4a1288065097231548e5b18e240508785-1302x1446.png" style="width: 100%;" /><p>Utilisons maintenant les paramètres de découpage définis ci-dessus pour découper le document en morceaux :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a3313c4c40ff39b8dbcdd7c4878c723f088e6c1a-1600x1187.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/79a2921943191ea631df94c9d465818ec8d3e738-1600x1206.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/459e695cce7540267422396b9a62ff4ad35f61db-1600x1260.png" style="width: 100%;" /><p>Remarque : la nouvelle ligne à la fin de chaque morceau (à l&#x27;exception du morceau 3) n&#x27;est pas mise en évidence, mais elle est incluse dans les limites du morceau.</p><h3>Commencez dès aujourd&#x27;hui à utiliser le découpage récursif !</h3><p>Pour plus d&#x27;informations sur l&#x27;utilisation de cette fonctionnalité, consultez la documentation sur la <span class="unknown__pt__mark__88e241676926">configuration des paramètres de regroupement.</span></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Présentation de l'interface utilisateur des règles de requête Elasticsearch dans Kibana]]></title>
            <description><![CDATA[Découvrez comment utiliser l'interface utilisateur Elasticsearch Query Rules pour ajouter ou exclure des documents des requêtes de recherche à l'aide d'ensembles de règles personnalisables dans Kibana, sans affecter le classement organique.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-query-rules-ui-introduction</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-query-rules-ui-introduction</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jhon Guzmán]]></dc:creator>
            <pubDate>Fri, 07 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1fb10bd31c509cc9b9bb4f71f49970f140e6c36f-1600x945.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Le rôle d&#x27;un moteur de recherche est de renvoyer des résultats pertinents. Cependant, certains besoins professionnels vont au-delà, comme la mise en évidence des ventes, la priorité donnée aux produits saisonniers ou la présentation d&#x27;articles sponsorisés, et les développeurs ne peuvent pas toujours le faire dans la requête de recherche.</p><p>En outre, ces cas d&#x27;utilisation sont généralement sensibles au temps, et passer par les étapes de développement habituelles (créer une branche de code et attendre une nouvelle version) est un processus qui prend beaucoup de temps.</p><p>Et si nous pouvions réaliser l&#x27;ensemble de ce processus par un simple appel d&#x27;API ou, mieux encore, en quelques clics dans Kibana ?</p><h2>Règles d&#x27;interrogation</h2><p>Elasticsearch 8.10 a introduit les <a href="https://www.elastic.co/blog/introducing-query-rules-elasticsearch-8-10"><strong>règles de requête</strong></a> et le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rule-retriever"><strong>récupérateur de règles</strong></a>. Il s&#x27;agit d&#x27;outils conçus pour injecter des <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-pinned-query"><em>résultats épinglés</em></a> dans les requêtes sans affecter le classement des résultats organiques sur la base de règles. Ils ne font qu&#x27;ajouter une logique d&#x27;entreprise aux résultats d&#x27;une manière simple et déclarative.</p><p>Voici quelques exemples d&#x27;utilisation courante des règles de requête :</p><ul><li><strong>Mise en évidence des annonces ou des ventes promues</strong>: Afficher les articles en vente ou sponsorisés en haut de la page.</li><li><strong>Exclusion en fonction du contexte ou de la géolocalisation</strong>: Masquer certains éléments lorsque la réglementation locale ne permet pas de les afficher.</li><li><strong>Donner la priorité aux résultats clés</strong>: Veiller à ce que les recherches populaires ou fixes soient toujours en tête, quel que soit le classement organique.</li></ul><p>Pour accéder à l&#x27;interface et interagir avec ces outils, vous devez cliquer sur le menu latéral de Kibana et aller dans <strong>Règles de requête</strong>, sous <strong>Pertinence</strong>:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/242e33e89d1a07ffa0e76009c46b3a9236722741-458x1010.png" style="width: 100%;" /><p>Lorsque le menu des règles de requête s&#x27;affiche, cliquez sur <strong>Créer votre premier jeu de règles :</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/30b3a91bbbf243d314cf38298e01ca5cff784430-1600x945.png" style="width: 100%;" /><p>Ensuite, vous devez nommer votre jeu de règles.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/26c5462f88678867776f933b5655ca0df0d72a16-708x446.png" style="width: 100%;" /><p>Le formulaire permettant de définir chaque règle comporte trois éléments clés :</p><ul><li><strong>Critères</strong>: Les conditions qui doivent être remplies pour que la règle s&#x27;applique. Par exemple, &quot;lorsque le champ query_string contient la valeur <em>Christmas</em>&quot; ou &quot;lorsque le champ country est <em>CO&quot;.</em></li><li><strong>Action</strong>: C&#x27;est ce que vous voulez qu&#x27;il se passe lorsque les conditions sont remplies. Il peut être épinglé (fixation d&#x27;un document dans les premiers résultats) ou exclu (masquage d&#x27;un document).</li><li><strong>Métadonnées</strong>: Il s&#x27;agit des champs qui accompagnent la requête lors de son exécution. Elles peuvent inclure des informations sur l&#x27;utilisateur (comme la localisation ou la langue) ainsi que des données de recherche (query_string). Il s&#x27;agit des valeurs utilisées par les critères pour décider d&#x27;appliquer ou non une règle.</li></ul><h2>Exemple : articles populaires</h2><p>Imaginons que nous ayons un site de commerce électronique proposant différents articles. En vérifiant les mesures, nous remarquons que l&#x27;un des articles les plus vendus dans la catégorie des consoles est la &quot;manette sans fil DualShock 4&quot;, en particulier lorsque les utilisateurs recherchent les mots clés &quot;PS4&quot; ou &quot;PlayStation 4&quot;. Nous décidons donc de placer ce produit en tête des résultats lorsqu&#x27;un utilisateur effectue une recherche avec ces mots-clés.</p><p>Tout d&#x27;abord, nous allons indexer les documents pour chaque article à l&#x27;aide d&#x27;une requête API en bloc :</p><pre><code>POST _bulk
{ "index": { "_index": "products", "_id": "1" } }
{ "id": "1", "name": "PlayStation 4 Slim 1TB", "category": "console", "brand": "Sony", "price": 1200 }
{ "index": { "_index": "products", "_id": "2" } }
{ "id": "2", "name": "DualShock 4 Wireless Controller", "category": "accessory", "brand": "Sony", "price": 250 }
{ "index": { "_index": "products", "_id": "3" } }
{ "id": "3", "name": "PlayStation 4 Camera", "category": "accessory", "brand": "Sony", "price": 200 }
{ "index": { "_index": "products", "_id": "4" } }
{ "id": "4", "name": "PlayStation 4 VR Headset", "category": "accessory", "brand": "Sony", "price": 900 }
{ "index": { "_index": "products", "_id": "5" } }
{ "id": "5", "name": "Charging Station for DualShock 4", "category": "accessory", "brand": "Sony", "price": 80 }</code></pre><p>Si nous n&#x27;intervenons pas dans la requête, l&#x27;article apparaît généralement en quatrième position. Voici la requête :</p><pre><code>GET products/_search
{
 "query": {
   "match": {
     "name": "PlayStation 4"
   }
 }
}</code></pre><p>Et voici les résultats</p><pre><code>{
 "took": 1,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 0.6973252,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "2",
       "_score": 0.08701137,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}</code></pre><p>Créons une règle de requête pour modifier cela. Tout d&#x27;abord, ajoutons-le au jeu de règles comme suit :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fdc42646fb3e76a09bca7d19047a76efe343f7a2-1600x650.png" style="width: 100%;" /><p>Ou <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-query-rules-put-ruleset">demande d&#x27;API</a> équivalente :</p><pre><code>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-1232",
      "type": "pinned",
      "criteria": [
        {
          "type": "exact",
          "metadata": "query_string",
          "values": [
            "PS4",
            "PlayStation 4"
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}</code></pre><p>Pour utiliser l&#x27;<strong>ensemble de règles </strong>dans notre requête, nous devons utiliser un type de règle de requête. Ce type de requête se compose de deux parties principales :</p><pre><code>GET /products/_search
{
 "retriever": {
   "rule": {
     "retriever": {
       "standard": {
         "query": {
           "match": { "name": "PlayStation 4" }
         }
       }
     },
     "match_criteria": {
       "query_string": "PlayStation 4"
     },
     "ruleset_ids": ["my-rules"]
   }
 }
}</code></pre><ul><li><strong>match_criteria</strong>: Il s&#x27;agit des métadonnées utilisées pour la comparaison avec la requête de l&#x27;utilisateur. Dans cet exemple, le jeu de règles est activé lorsque le champ query_string a la valeur &quot;PlayStation 4&quot;.</li><li><strong>requête</strong>: la requête réelle qui sera utilisée pour effectuer la recherche et obtenir les résultats organiques.</li></ul><p>De cette façon, vous exécutez d&#x27;abord la requête organique, puis Elasticsearch applique les règles de votre ensemble de règles :</p><pre><code>{
 "took": 17,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 1.7014122e+38,
   "hits": [
     {
       "_index": "products",
       "_id": "2",
       "_score": 1.7014122e+38,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}</code></pre><h2>Exemple : métadonnées basées sur l&#x27;utilisateur</h2><p>Une autre application intéressante des règles d&#x27;interrogation consiste à utiliser les métadonnées pour afficher des documents spécifiques sur la base d&#x27;informations contextuelles provenant de l&#x27;utilisateur ou de la page web.</p><p>Par exemple, imaginons que nous souhaitions mettre en avant des articles ou des ventes personnalisées en fonction du niveau de fidélité d&#x27;un utilisateur, représenté par une valeur numérique.</p><p>Nous pouvons le faire en intégrant ces métadonnées directement dans la requête, de sorte que les règles s&#x27;activent lorsque la valeur en question répond à certains critères.</p><p>Tout d&#x27;abord, nous allons indexer un document que seuls les utilisateurs ayant un niveau de fidélité élevé peuvent consulter :</p><pre><code>POST _bulk
{ "index": { "_index": "products", "_id": "6" } }
{ "id": "6", "name": "PlayStation Plus Deluxe Card - 12 months", "category": "membership", "brand": "Sony", "price": 300 }</code></pre><p>Maintenant, créons une nouvelle règle dans le même jeu de règles pour que lorsque le niveau de fidélité est égal ou supérieur à 80, l&#x27;article apparaisse en tête des résultats.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/58de12dff93305440608f51465462fcc68653a08-1421x496.png" style="width: 100%;" /><p>Enregistrez la règle et le jeu de règles.</p><p>Voici la requête REST équivalente :</p><pre><code>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "pin-premiun-user",
      "type": "pinned",
      "criteria": [
        {
          "type": "gte",
          "metadata": "loyalty_level",
          "values": [
            80
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "6"
          }
        ]
      }
    }
  ]
}</code></pre><p>Désormais, lors de l&#x27;exécution d&#x27;une requête, nous devons inclure le nouveau paramètre <strong>loyalty_level </strong>dans les métadonnées. Si la condition de la règle est remplie, le nouveau document apparaît en tête des résultats.</p><p>Par exemple, lors de l&#x27;envoi d&#x27;une requête dont le niveau de fidélité est 80 :</p><pre><code>POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 80
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}</code></pre><p>Nous verrons le document de fidélisation en haut des résultats :</p><pre><code>{
  "took": 31,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 1.7014122e+38,
    "hits": [
      {
        "_index": "products",
        "_id": "6",
        "_score": 1.7014122e+38,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      },
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      }
    ]
  }
}</code></pre><p>Dans le cas ci-dessous, le niveau de fidélité étant de 70, la règle n&#x27;est pas respectée et l&#x27;objet ne doit pas apparaître en haut de la liste :</p><pre><code>POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 70
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}</code></pre><p>Voici les résultats :</p><pre><code>{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 0.5054567,
    "hits": [
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      },
      {
        "_index": "products",
        "_id": "6",
        "_score": 0.3817649,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      }
    ]
  }
}</code></pre><h2>Exemple : exclusion immédiate</h2><p>Supposons que notre <strong>manette sans fil DualShock 4 (ID 2)</strong> soit temporairement indisponible et ne puisse être vendue. Ainsi, au lieu de supprimer manuellement le document ou d&#x27;attendre qu&#x27;un processus de données se mette en place, l&#x27;équipe commerciale décide de le supprimer des résultats de recherche en attendant.</p><p>Nous utiliserons un processus similaire à celui que nous venons d&#x27;appliquer aux articles populaires, mais cette fois-ci, au lieu de sélectionner <em>Épinglé</em>, nous choisirons <em>Exclure</em>. Cette règle fonctionne comme une sorte de liste noire. Changez les critères en <strong>Toujours</strong> pour que l&#x27;exclusion fonctionne à chaque fois que la requête est exécutée.</p><p>La règle devrait ressembler à ceci :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f10971e4f1bc9520105111adfa3a476581a27130-1600x623.png" style="width: 100%;" /><p>Enregistrez la règle et le jeu de règles pour appliquer les modifications. Voici la requête REST équivalente :</p><pre><code>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-6358",
      "type": "pinned",
      "criteria": [
        {
          "type": "always"
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}</code></pre><p>Maintenant, lorsque nous exécutons à nouveau la requête, vous verrez que l&#x27;élément ne figure plus dans les résultats, bien que la règle préalable soit de l&#x27;épingler. En effet, <strong>les exclusions ont la priorité sur les résultats de l&#x27;épinglage</strong>.</p><pre><code>{
 "took": 6,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 4,
     "relation": "eq"
   },
   "max_score": 2.205655,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 2.205655,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 1.9738505,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 1.9738505,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.69247496,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}</code></pre><h2>Conclusion</h2><p>Les <strong>règles de requête</strong> permettent d&#x27;ajuster très facilement la pertinence sans modifier le code. La nouvelle interface <strong>utilisateur</strong> <strong>Kibana </strong>vous permet<strong> </strong>d&#x27;effectuer ces modifications en quelques secondes, ce qui vous donne, ainsi qu&#x27;à votre équipe commerciale, un meilleur contrôle sur vos résultats de recherche.</p><p>Au-delà du commerce électronique, les règles de requête peuvent servir à de nombreux autres scénarios : mise en évidence des guides de dépannage dans les portails d&#x27;assistance, mise en évidence des documents internes clés dans les bases de connaissances, promotion des dernières nouvelles dans les sites d&#x27;information ou filtrage des offres d&#x27;emploi ou des listes de contenu expirées. Ils peuvent même appliquer des règles de conformité, par exemple en masquant les documents à diffusion restreinte en fonction du rôle de l&#x27;utilisateur ou de la région.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un agent de connaissance avec rappel sémantique en utilisant Mastra et Elasticsearch]]></title>
            <description><![CDATA[Apprenez à construire un agent de connaissance avec rappel sémantique en utilisant Mastra et Elasticsearch comme magasin vectoriel pour la mémoire et la recherche d'informations.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b8d51c2726d5573385c9246a7821d12ade4f1b0e-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>L&#x27;<a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">ingénierie contextuelle</a> devient de plus en plus importante dans la construction d&#x27;agents et d&#x27;architectures d&#x27;IA fiables. Au fur et à mesure que les modèles s&#x27;améliorent, leur efficacité et leur fiabilité dépendent moins de leurs données d&#x27;entraînement que de leur ancrage dans le bon contexte. Les agents qui peuvent récupérer et appliquer les informations les plus pertinentes au bon moment sont beaucoup plus susceptibles de produire des résultats précis et fiables.</p><p>Dans ce blog, nous utiliserons <a href="https://mastra.ai/">Mastra</a> pour construire un agent de connaissance qui se souvient de ce que les utilisateurs disent et peut rappeler les informations pertinentes plus tard, en utilisant Elasticsearch comme mémoire et backend de récupération. Vous pouvez facilement étendre ce même concept à des cas d&#x27;utilisation réels, comme des agents d&#x27;assistance qui peuvent se souvenir de conversations et de résolutions antérieures, ce qui leur permet d&#x27;adapter les réponses à des utilisateurs spécifiques ou de trouver des solutions plus rapidement en fonction du contexte antérieur.</p><p>Suivez ici les étapes de sa construction. Si vous vous perdez ou si vous voulez simplement exécuter un exemple fini, consultez le repo <a href="https://github.com/jdarmada/getting-started-mastra-elastic/tree/main">ici.</a></p><h2>Qu&#x27;est-ce que Mastra ?</h2><p>Mastra est un framework TypeScript open-source pour la construction d&#x27;agents d&#x27;intelligence artificielle avec des parties interchangeables pour le raisonnement, la mémoire et les outils. Sa fonction de <a href="https://mastra.ai/docs/memory/semantic-recall">rappel sémantique</a> permet aux agents de se souvenir des interactions passées et de les retrouver en stockant les messages sous forme d&#x27;enchâssements dans une base de données vectorielle. Cela permet aux agents de conserver le contexte et la continuité de la conversation à long terme. Elasticsearch est un excellent magasin de vecteurs pour activer cette fonctionnalité, car il prend en charge la recherche vectorielle dense efficace. Lorsque le rappel sémantique est déclenché, l&#x27;agent introduit les messages antérieurs pertinents dans la fenêtre contextuelle du modèle, ce qui permet à ce dernier d&#x27;utiliser le contexte récupéré comme base de son raisonnement et de ses réponses.</p><h2>Ce qu&#x27;il faut pour commencer</h2><ul><li>Node v18+</li><li>Elasticsearch (version 8.15 ou plus récente)</li><li>Clé API Elasticsearch</li><li><a href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key">Clé API OpenAI</a></li></ul><p>Note : Vous en aurez besoin parce que la démo utilise le fournisseur OpenAI, mais Mastra prend en charge d&#x27;autres SDK d&#x27;IA et fournisseurs de modèles communautaires, vous pouvez donc facilement l&#x27;échanger en fonction de votre configuration.</p><h2>Construire un projet Mastra</h2><p>Nous utiliserons le CLI intégré de Mastra pour fournir l&#x27;échafaudage de notre projet. Exécutez la commande :</p><pre><code>npm create mastra@latest</code></pre><p>Vous obtiendrez une série d&#x27;invites, commençant par :</p><p>1. Donnez un nom à votre projet.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2b9fe559e0276140dd539e24f916a73c60870405-620x84.png" style="width: 100%;" /><p>2. Nous pouvons conserver cette valeur par défaut ; n&#x27;hésitez pas à la laisser vide.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e04729eb03bce8499e973e18c28642402340d0e5-852x68.png" style="width: 100%;" /><p>3. Pour ce projet, nous utiliserons un modèle fourni par OpenAI.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a86596a469a71bdf8bd99cbaf528d0f0cf7272c0-436x222.png" style="width: 100%;" /><p>4. Sélectionnez l&#x27;option &quot;Skip for now&quot; car nous allons stocker toutes nos variables d&#x27;environnement dans un fichier `.env` que nous configurerons plus tard.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/02b19ccc34af0bdacf52fd94b519d036540ca2e6-426x114.png" style="width: 100%;" /><p>5. Nous pouvons également ignorer cette option.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b3fe63d19d270bc2e0de1dd92033bf8b26750819-990x208.png" style="width: 100%;" /><p>Une fois l&#x27;initialisation terminée, nous pouvons passer à l&#x27;étape suivante.</p><h3>Installation des dépendances</h3><p>Ensuite, nous devons installer quelques dépendances :</p><pre><code>npm install ai @ai-sdk/openai @elastic/elasticsearch dotenv</code></pre><ul><li><code>ai</code> - Ensemble de SDK d&#x27;IA de base qui fournit des outils pour gérer les modèles d&#x27;IA, les invites et les flux de travail en JavaScript/TypeScript. Mastra est construit sur le <a href="https://ai-sdk.dev/">SDK AI</a> de Vercel, nous avons donc besoin de cette dépendance pour permettre les interactions du modèle avec votre agent.</li><li><code>@ai-sdk/openai</code> - Plugin qui connecte le SDK AI aux modèles OpenAI (comme GPT-4, GPT-4o, etc.), permettant des appels API en utilisant votre clé API OpenAI.</li><li><code>@elastic/elasticsearch</code> - <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript">Client Elasticsearch officiel pour Node.js</a>, utilisé pour se connecter à votre Elastic Cloud ou à votre cluster local pour l&#x27;indexation, la recherche et les opérations vectorielles.</li><li><code>dotenv</code> - Charge les variables d&#x27;environnement à partir d&#x27;un fichier .env dans le fichier process.env, vous permettant d&#x27;injecter en toute sécurité des informations d&#x27;identification telles que des clés d&#x27;API et des points d&#x27;extrémité Elasticsearch.</li></ul><h3>Configuration des variables d&#x27;environnement</h3><p>Créez un fichier <code>.env</code> dans le répertoire racine de votre projet si vous n&#x27;en avez pas déjà un. Vous pouvez également copier et renommer l&#x27;exemple <code>.env</code> que j&#x27;ai fourni dans le <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/.env.example">répertoire.</a> Dans ce fichier, nous pouvons ajouter les variables suivantes :</p><pre><code>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"</code></pre><p>Voilà qui conclut la configuration de base. À partir de là, vous pouvez déjà commencer à construire et à orchestrer des agents. Nous allons aller plus loin et ajouter Elasticsearch en tant que couche de stockage et de recherche vectorielle.</p><h2>Ajouter Elasticsearch comme magasin de vecteurs</h2><p>Créez un nouveau dossier appelé <code>stores</code> et ajoutez-y ce <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/src/mastra/stores/elastic-store.ts">fichier</a>. Avant que Mastra et Elastic ne proposent une intégration officielle de Elasticsearch vector store, <a href="https://github.com/abhiaiyer91">Abhi Aiyer</a>(Mastra CTO) a partagé ce prototype de classe appelé <code>ElasticVector</code>. Simplement, il relie l&#x27;abstraction mémoire de Mastra aux capacités vectorielles denses d&#x27;Elasticsearch, de sorte que les développeurs peuvent utiliser Elasticsearch comme base de données vectorielle pour leurs agents.</p><p>Examinons plus en détail les éléments importants de l&#x27;intégration :</p><h3>Ingestion du client Elasticsearch</h3><p>Cette section définit la classe <code>ElasticVector</code> et met en place la connexion du client Elasticsearch avec un support pour les déploiements standards et sans serveur.</p><pre><code>export interface ElasticVectorConfig extends ClientOptions {
    /**
     * Explicitly specify if connecting to Elasticsearch Serverless.
     * If not provided, will be auto-detected on first use.
     */
    isServerless?: boolean;
    
    /**
     * Maximum documents to count accurately when describing indices.
     * Higher values provide accurate counts but may impact performance on large indices.
     * 
     * @default 10000
     */
    maxCountAccuracy?: number;
}

export class ElasticVector extends MastraVector {
    private client: Client;
    private isServerless: boolean | undefined;
    private deploymentChecked: boolean = false;
    private readonly maxCountAccuracy: number;

    constructor(config: ElasticVectorConfig) {
        super();
        this.client = new Client(config);
        this.isServerless = config.isServerless;
        this.maxCountAccuracy = config.maxCountAccuracy ?? 10000;
    }
}</code></pre><ul><li><code>ElasticVectorConfig extends ClientOptions</code>: Ceci crée une nouvelle interface de configuration qui hérite de toutes les options du client Elasticsearch (comme <code>node</code>, <code>auth</code>, <code>requestTimeout</code>) et ajoute nos propriétés personnalisées. Cela signifie que les utilisateurs peuvent passer n&#x27;importe quelle configuration Elasticsearch valide avec nos options spécifiques au serveur.</li><li><code>extends MastraVector</code>: Cela permet à <code>ElasticVector</code> d&#x27;hériter de la classe de base <code>MastraVector</code> de Mastra, qui est une interface commune à laquelle se conforment toutes les intégrations de magasins vectoriels. Cela garantit qu&#x27;Elasticsearch se comporte comme n&#x27;importe quel autre backend vectoriel Mastra du point de vue de l&#x27;agent.</li><li><code>private client: Client</code>: Il s&#x27;agit d&#x27;une propriété privée qui contient une instance du client JavaScript Elasticsearch. Cela permet à la classe de s&#x27;adresser directement à votre cluster.</li><li><code>isServerless</code> et <code>deploymentChecked</code>: Ces propriétés fonctionnent ensemble pour détecter et mettre en cache si nous sommes connectés à un déploiement Elasticsearch standard ou sans serveur. Cette détection se fait automatiquement lors de la première utilisation ou peut être configurée explicitement.</li><li><code>constructor(config: ClientOptions)</code>: Ce constructeur prend un objet de configuration (contenant vos identifiants Elasticsearch et des paramètres serverless optionnels) et l&#x27;utilise pour initialiser le client dans la ligne <code>this.client = new Client(config)</code>.</li><li><code>super()</code>: Il appelle le constructeur de base de Mastra, ce qui lui permet d&#x27;hériter de la journalisation, des aides à la validation et d&#x27;autres crochets internes.</li></ul><p>À ce stade, Mastra sait qu&#x27;il existe un nouveau magasin de vecteurs appelé <code>ElasticVector</code></p><h3>Détection du type de déploiement</h3><p>Avant de créer des index, l&#x27;adaptateur détecte automatiquement si vous utilisez Elasticsearch standard ou Elasticsearch Serverless. C&#x27;est important car les déploiements sans serveur ne permettent pas la configuration manuelle des shards.</p><pre><code>private async detectServerless(): Promise<boolean> {
    // Return cached result if already detected
    if (this.deploymentChecked) {
        return this.isServerless ?? false;
    }

    // Use explicit configuration if provided
    if (this.isServerless !== undefined) {
        this.deploymentChecked = true;
        this.logger?.info(
            `Using explicit deployment type: ${this.isServerless ? 'Serverless' : 'Standard'}`
        );
        return this.isServerless;
    }

    try {
        const info = await this.client.info();
        
        // Primary detection: build flavor (most reliable)
        const isBuildFlavorServerless = info.version?.build_flavor === 'serverless';
        
        // Secondary detection: tagline (fallback)
        const isTaglineServerless = info.tagline?.toLowerCase().includes('serverless') ?? false;
        
        this.isServerless = isBuildFlavorServerless || isTaglineServerless;
        this.deploymentChecked = true;
        
        this.logger?.info(
            `Auto-detected ${this.isServerless ? 'Serverless' : 'Standard'} Elasticsearch deployment`,
            { 
                buildFlavor: info.version?.build_flavor, 
                version: info.version?.number,
                detectionMethod: isBuildFlavorServerless ? 'build_flavor' : 'tagline'
            }
        );
        
        return this.isServerless;
    } catch (error) {
        this.logger?.warn(
            'Could not auto-detect deployment type, assuming Standard Elasticsearch. ' +
            'Set isServerless: true explicitly in config if using Serverless.',
            { error: error instanceof Error ? error.message : String(error) }
        );
        this.isServerless = false;
        this.deploymentChecked = true;
        return false;
    }
}</code></pre><p>Ce qui se passe :</p><ul><li>Vérifie d&#x27;abord si vous avez explicitement défini <code>isServerless</code> dans la configuration (ignore l&#x27;autodétection).</li><li>Appelle l&#x27;API <code>info()</code> d&#x27;Elasticsearch pour obtenir des informations sur les clusters.</li><li>Vérifie le <code>build_flavor field</code> (les déploiements sans serveur renvoient <code>serverless</code>).</li><li>Renvoie à la vérification du slogan si la saveur de la construction n&#x27;est pas disponible</li><li>Met en cache le résultat afin d&#x27;éviter les appels répétés à l&#x27;API</li><li>Déploiement standard par défaut en cas d&#x27;échec de la détection</li></ul><p> Exemple d&#x27;utilisation :</p><pre><code>// Option 1: Auto-detect (recommended)
const vector = new ElasticVector({
    node: 'https://your-cluster.es.cloud',
    auth: { apiKey: 'your-api-key' }
});
// Detection happens automatically on first index operation

// Option 2: Explicit configuration (faster startup)
const vector = new ElasticVector({
    node: 'https://your-serverless.es.cloud',
    auth: { apiKey: 'your-api-key' },
    isServerless: true  // Skips auto-detection
});</code></pre><h3>Création du magasin &quot;memory&quot; dans Elasticsearch</h3><p>La fonction ci-dessous met en place un index Elasticsearch pour le stockage des embeddings. Il vérifie si l&#x27;index existe déjà. Si ce n&#x27;est pas le cas, il en crée un avec le mappage ci-dessous qui contient un champ <code>dense_vector</code> pour stocker les embeddings et les métriques de similarité personnalisées.</p><p>Quelques points à noter :</p><ul><li>Le paramètre <code>dimension</code> est la longueur de chaque vecteur d&#x27;intégration, qui dépend du modèle d&#x27;intégration utilisé. Dans notre cas, nous allons générer des embeddings en utilisant le modèle <code>text-embedding-3-small</code> d&#x27;OpenAI, qui produit des vecteurs de taille <code>1536</code>. Nous l&#x27;utiliserons comme valeur par défaut.</li><li>La variable <code>similarity</code> utilisée dans la correspondance ci-dessous est définie à partir de la fonction d&#x27;aide c<code>onst similarity = this.mapMetricToSimilarity(metric)</code>, qui prend la valeur du paramètre <code>metric</code> et la convertit en un mot-clé compatible avec Elasticsearch pour la métrique de distance choisie.<ul><li>Par exemple : Mastra utilise des termes généraux pour la similarité vectorielle comme <code>cosine</code>, <code>euclidean</code>, et <code>dotproduct</code>. Si nous devions passer la métrique <code>euclidean</code> directement dans le mappage Elasticsearch, une erreur se produirait car Elasticsearch s&#x27;attend à ce que le mot-clé <code>l2_norm</code> représente la distance euclidienne.</li></ul></li><li>Compatibilité sans serveur : Le code omet automatiquement les paramètres de shard et de réplique pour les déploiements sans serveur, car ils sont gérés automatiquement par Elasticsearch Serverless.</li></ul><pre><code>async createIndex(params: CreateIndexParams): Promise<void> {
    const { indexName, dimension = 1536, metric = 'cosine' } = params;

    try {
        const exists = await this.client.indices.exists({ index: indexName });

        if (exists) {
            try {
                await this.validateExistingIndex(indexName, dimension, metric);
                this.logger?.info(`Index "${indexName}" already exists and is valid`);
                return;
            } catch (validationError) {
                throw new Error(
                    `Index "${indexName}" exists but does not match the required configuration: ${
                        validationError instanceof Error ? validationError.message : String(validationError)
                    }`
                );
            }
        }

        const isServerless = await this.detectServerless();
        const similarity = this.mapMetricToSimilarity(metric);

        const indexConfig: any = {
            index: indexName,
            mappings: {
                properties: {
                    vector: {
                        type: 'dense_vector',
                        dims: dimension,
                        index: true,
                        similarity: similarity,
                    },
                    metadata: {
                        type: 'object',
                        enabled: true,
                        dynamic: true, // Allows flexible metadata structures
                    },
                },
            },
        };

        // Only configure shards/replicas for non-serverless deployments
        // Serverless manages infrastructure automatically
        if (!isServerless) {
            indexConfig.settings = {
                number_of_shards: 1,
                number_of_replicas: 0, // Increase for production HA deployments
            };
        }

        await this.client.indices.create(indexConfig);

        this.logger?.info(
            `Created ${isServerless ? 'Serverless' : 'Standard'} Elasticsearch index "${indexName}"`,
            { dimension, metric, similarity }
        );
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to create index "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to create index "${indexName}": ${errorMessage}`);
    }
}</code></pre><h3>Enregistrement d&#x27;un nouveau souvenir ou d&#x27;une nouvelle note après une interaction</h3><p>Cette fonction prend les nouveaux embeddings générés après chaque interaction, ainsi que les métadonnées, puis les insère ou les met à jour dans l&#x27;index à l&#x27;aide de l&#x27;API <code>bulk</code> d&#x27;Elastic. L&#x27;API <code>bulk</code> regroupe plusieurs opérations d&#x27;écriture en une seule demande ; cette amélioration de nos performances d&#x27;indexation garantit que les mises à jour restent efficaces alors que la mémoire de notre agent ne cesse de croître.</p><pre><code>async upsert(params: UpsertVectorParams): Promise<string[]> {
    const { indexName, vectors, metadata = [], ids } = params;

    try {
        // Generate unique IDs if not provided
        const vectorIds = ids || vectors.map((_, i) => 
            `vec_${Date.now()}_${i}_${Math.random().toString(36).substr(2, 9)}`
        );

        const operations = vectors.flatMap((vec, index) => [
            { index: { _index: indexName, _id: vectorIds[index] } },
            {
                vector: vec,
                metadata: metadata[index] || {},
            },
        ]);

        const response = await this.client.bulk({
            refresh: true,
            operations,
        });

        if (response.errors) {
            const erroredItems = response.items.filter((item: any) => item.index?.error);
            const erroredIds = erroredItems.map((item: any) => item.index?._id);
            const errorDetails = erroredItems.slice(0, 3).map((item: any) => ({
                id: item.index?._id,
                error: item.index?.error?.reason || item.index?.error,
                type: item.index?.error?.type
            }));
            
            const errorMessage = `Failed to upsert ${erroredIds.length}/${vectors.length} vectors`;
            console.error(`${errorMessage}. Sample errors:`, JSON.stringify(errorDetails, null, 2));
            this.logger?.error(errorMessage, { 
                failedCount: erroredIds.length, 
                totalCount: vectors.length,
                sampleErrors: errorDetails 
            });
            
            // Still return successfully inserted IDs
            const successfulIds = vectorIds.filter((id, idx) => 
                !erroredIds.includes(id)
            );
            
            if (successfulIds.length === 0) {
                throw new Error(`${errorMessage}. All operations failed. See logs for details.`);
            }
            
            return successfulIds;
        }

        this.logger?.info(`Successfully upserted ${vectors.length} vectors to "${indexName}"`);
        return vectorIds;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
    }
}</code></pre><h3>Interrogation des vecteurs similaires pour le rappel sémantique</h3><p>Cette fonction est au cœur de la fonction de rappel sémantique. L&#x27;agent utilise la recherche vectorielle pour trouver des enregistrements similaires dans notre index.</p><pre><code>async query(params: QueryVectorParams<any>): Promise<QueryResult[]> {
    const { indexName, queryVector, topK = 10, filter, includeVector = false } = params;

    try {
        const knnQuery: any = {
            field: 'vector',
            query_vector: queryVector,
            k: topK,
            num_candidates: Math.max(topK * 10, 100), // Search more candidates for better recall
        };

        // Apply metadata filters if provided
        if (filter) {
            knnQuery.filter = this.buildElasticFilter(filter);
        }

        const sourceFields = ['metadata'];
        if (includeVector) {
            sourceFields.push('vector');
        }

        const response = await this.client.search({
            index: indexName,
            knn: knnQuery,
            size: topK,
            _source: sourceFields,
        });

        const results = response.hits.hits.map((hit: any) => ({
            id: hit._id,
            score: hit._score || 0,
            metadata: hit._source?.metadata || {},
            vector: includeVector ? hit._source?.vector : undefined,
        }));

        this.logger?.debug(`Query returned ${results.length} results from "${indexName}"`);
        return results;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
    }
}</code></pre><p>Sous le capot :</p><ul><li>Exécute une requête <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a> (k-nearest neighbors) à l&#x27;aide de l&#x27;API <code>knn</code> dans Elasticsearch.</li><li>Récupère les K premiers vecteurs similaires au vecteur d&#x27;entrée de la requête.</li><li>Possibilité d&#x27;appliquer des filtres de métadonnées pour limiter les résultats (par exemple, recherche uniquement dans une catégorie ou une période spécifique).</li><li>Renvoie des résultats structurés comprenant l&#x27;identifiant du document, le score de similarité et les métadonnées stockées.</li></ul><h2>Création de l&#x27;agent de connaissance</h2><p>Maintenant que nous avons vu la connexion entre Mastra et Elasticsearch à travers l&#x27;intégration <code>ElasticVector</code>, créons l&#x27;agent de connaissance lui-même.</p><p>Dans le dossier <code>agents</code>, créez un fichier appelé <code>knowledge-agent.ts</code>. Nous pouvons commencer par connecter nos variables d&#x27;environnement et initialiser le client Elasticsearch.</p><pre><code>import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { openai } from '@ai-sdk/openai';
import { Client } from '@elastic/elasticsearch';
import { ElasticVector } from '../stores/elastic-store';
import dotenv from "dotenv";

dotenv.config();

const ELASTICSEARCH_ENDPOINT = process.env.ELASTICSEARCH_ENDPOINT;
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY;

//Error check for undefined credentials
if (!ELASTICSEARCH_ENDPOINT || !ELASTICSEARCH_API_KEY) {
  throw new Error('Missing Elasticsearch credentials');
}

//Check to see if a connection can be established
const testClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { 
    apiKey: ELASTICSEARCH_API_KEY 
  },
});

try {
  await testClient.ping();
  console.log('Connected to Elasticsearch successfully');
} catch (error: unknown) {
  if (error instanceof Error) {
    console.error('Failed to connect to Elasticsearch:', error.message);
  } else {
    console.error('Failed to connect to Elasticsearch:', error);
  }
  process.exit(1);
}
//Initialize the Elasticsearch vector store
const vectorStore = new ElasticVector({
  node: ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: ELASTICSEARCH_API_KEY,
  },
//Optional: Explicitly set to true if using Elasticsearch Serverless to skip auto-detection and improve startup time
//isServerless: true,
});</code></pre><p>Ici, nous :</p><ul><li>Utilisez <code>dotenv</code> pour charger nos variables à partir de notre fichier <code>.env</code>.</li><li>Vérifiez que les informations d&#x27;identification Elasticsearch sont injectées correctement et que nous pouvons établir une connexion réussie avec le client.</li><li>Passez le point de terminaison Elasticsearch et la clé API dans le constructeur <code>ElasticVector</code> pour créer une instance de notre magasin vectoriel que nous avons défini plus tôt.</li><li>Spécifiez éventuellement <code>isServerless: true</code> si vous utilisez Elasticsearch Serverless. Cela permet d&#x27;éviter l&#x27;étape d&#x27;autodétection et d&#x27;améliorer le temps de démarrage. S&#x27;il est omis, l&#x27;adaptateur détectera automatiquement votre type de déploiement lors de la première utilisation.</li></ul><p>Ensuite, nous pouvons définir l&#x27;agent à l&#x27;aide de la classe <code>Agent</code> de Mastra.</p><pre><code>export const knowledgeAgent = new Agent({
    name: 'KnowledgeAgent',
    instructions: 'You are a helpful knowledge assistant.',
    model: openai('gpt-4o'),
    memory: new Memory({

        vector: vectorStore,

        //embedder used to create embeddings for each message
        embedder: 'openai/text-embedding-3-small',

        //set semantic recall options
        options: {
            semanticRecall: {
                topK: 3, // retrieve 3 similar messages
                messageRange: 2, // include 2 messages before/after each match
                scope: 'resource',
            },
        },
    }),
});</code></pre><p>Les champs que nous pouvons définir sont les suivants :</p><ul><li><code>name</code> et <code>instructions</code>: lui donner une identité et une fonction première.</li><li><code>model</code>: Nous utilisons <code>gpt-4o</code> d&#x27;OpenAI à travers le paquet <code>@ai-sdk/openai</code>.</li><li><code>memory</code>:<ul><li><code>vector</code>: Pointe vers notre magasin Elasticsearch, de sorte que les embeddings sont stockés et récupérés à partir de ce magasin.</li><li><code>embedder</code>: Quel modèle utiliser pour générer des embeddings ?</li><li><code>semanticRecall</code> décident de la manière dont le rappel fonctionne :<ul><li><code>topK</code>: Nombre de messages sémantiquement similaires à récupérer.</li><li><code>messageRange</code>: Quelle partie de la conversation doit être incluse dans chaque match.</li><li><code>scope</code>: Définit la limite de la mémoire.</li></ul></li></ul></li></ul><p>Presque terminé. Il ne nous reste plus qu&#x27;à ajouter cet agent nouvellement créé à notre configuration Mastra. Dans le fichier appelé <a href="http://index.ts/"><code>index.ts</code></a>, importez l&#x27;agent de connaissance et insérez-le dans le champ <code>agents</code>.</p><pre><code>export const mastra = new Mastra({
  agents: { knowledgeAgent },
  storage: new LibSQLStore({
    // stores observability, scores, ... into memory storage, if it needs to persist, change to file:../mastra.db
    url: ":memory:",
  }),
  logger: new PinoLogger({
    name: 'Mastra',
    level: 'info',
  }),
  telemetry: {
    // Telemetry is deprecated and will be removed in the Nov 4th release
    enabled: false, 
  },
  observability: {
    // Enables DefaultExporter and CloudExporter for AI tracing
    default: { enabled: true }, 
  },
});</code></pre><p>Les autres champs sont les suivants :</p><ul><li><code>storage</code>: Il s&#x27;agit du magasin de données interne de Mastra pour l&#x27;historique des exécutions, les mesures d&#x27;observabilité, les scores et les caches. Pour plus d&#x27;informations sur le stockage Mastra, <a href="https://mastra.ai/docs/server-db/storage">cliquez ici.</a></li><li><code>logger</code>: Mastra utilise <a href="https://github.com/pinojs/pino">Pino</a>, qui est un enregistreur JSON structuré et léger. Il capture des événements tels que le démarrage et l&#x27;arrêt de l&#x27;agent, les appels d&#x27;outils et les résultats, les erreurs et les temps de réponse du LLM.</li><li><code>observability</code>: Contrôle le suivi de l&#x27;IA et la visibilité de l&#x27;exécution pour les agents. Il suit :<ul><li>Début/fin de chaque étape du raisonnement.</li><li>Quel modèle ou outil a été utilisé.</li><li>Entrées et sorties.</li><li>Notes et évaluations</li></ul></li></ul><h3>Test de l&#x27;agent avec Mastra Studio</h3><p>Félicitations ! Si vous êtes arrivé jusqu&#x27;ici, vous êtes prêt à faire fonctionner cet agent et à tester ses capacités de rappel sémantique. Heureusement, Mastra fournit une interface de chat intégrée, ce qui nous évite d&#x27;avoir à créer notre propre interface.</p><p>Pour démarrer le serveur de développement Mastra, ouvrez un terminal et exécutez la commande suivante :</p><pre><code>npm run dev</code></pre><p>Après le regroupement initial et le démarrage du serveur, celui-ci devrait vous fournir une adresse pour le terrain de jeu.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8b045f70008d26aec4d2e6b59d61085555b9c5b2-686x116.png" style="width: 100%;" /><p>Collez cette adresse dans votre navigateur et vous serez accueilli par le Mastra Studio.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/69bc80fe8486edd9e0cf91d87b39f465aeb23111-1600x438.png" style="width: 100%;" /><p>Sélectionnez l&#x27;option <code>knowledgeAgent</code> et discutez.</p><p>Pour vérifier rapidement si tout est bien branché, donnez-lui des informations telles que : &quot;L&#x27;équipe a annoncé que les ventes d&#x27;octobre ont augmenté de 12%, principalement grâce aux renouvellements de contrats d&#x27;entreprise. La prochaine étape consistera à élargir le champ d&#x27;action aux clients du marché intermédiaire&quot;. Ensuite, démarrez un nouveau chat et posez une question du type : &quot;Sur quel segment de clientèle avons-nous dit que nous devions nous concentrer ensuite ?&quot;. L&#x27;agent de connaissance doit pouvoir se souvenir des informations que vous lui avez communiquées lors de la première conversation. Vous devriez obtenir une réponse du type</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/da8ebddad89874023ed440a8f1ad2cb04ed043f4-1070x288.png" style="width: 100%;" /><p>Une telle réponse signifie que l&#x27;agent a stocké avec succès notre message précédent sous forme d&#x27;éléments intégrés dans Elasticsearch et qu&#x27;il l&#x27;a récupéré ultérieurement à l&#x27;aide d&#x27;une recherche vectorielle.</p><h3>Inspection de la mémoire à long terme de l&#x27;agent</h3><p>Rendez-vous sur l&#x27;onglet <code>memory</code> dans la configuration de votre agent dans le Studio Mastra. Cela vous permet de voir ce que votre agent a appris au fil du temps. Chaque message, réponse et interaction qui est intégré et stocké dans Elasticsearch fait partie de cette mémoire à long terme. Vous pouvez effectuer une recherche sémantique dans les interactions passées pour retrouver rapidement les informations ou le contexte que l&#x27;agent a appris précédemment. Il s&#x27;agit essentiellement du même mécanisme que celui utilisé par l&#x27;agent lors du rappel sémantique, mais ici, vous pouvez l&#x27;inspecter directement. Dans l&#x27;exemple ci-dessous, nous recherchons le terme &quot;ventes&quot; et nous obtenons en retour toutes les interactions qui contiennent un élément relatif aux ventes.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3decaa0c332d288c5ae0b11c25f592c7d50c2f0f-1104x1320.png" style="width: 100%;" /><h2>Conclusion</h2><p>En connectant Mastra et Elasticsearch, nous pouvons donner à nos agents de la mémoire, qui est une couche clé dans l&#x27;ingénierie contextuelle. Grâce au rappel sémantique, les agents peuvent construire un contexte au fil du temps, en fondant leurs réponses sur ce qu&#x27;ils ont appris. Cela signifie des interactions plus précises, plus fiables et plus naturelles.</p><p>Cette intégration précoce n&#x27;est que le point de départ. Le même modèle peut permettre aux agents d&#x27;assistance de se souvenir des tickets précédents, aux robots internes de retrouver la documentation pertinente ou aux assistants d&#x27;IA de se souvenir des détails d&#x27;un client au cours d&#x27;une conversation. Nous travaillons également à l&#x27;intégration officielle de Mastra, afin de rendre cette association encore plus transparente dans un avenir proche.</p><p>Nous sommes impatients de voir ce que vous allez construire. Essayez-le, explorez <a href="https://mastra.ai/">Mastra</a> et ses fonctions de mémoire, et n&#x27;hésitez pas à partager vos découvertes avec la communauté.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/multimodal-search-siglip-2-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/multimodal-search-siglip-2-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
            <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Avez-vous déjà voulu rechercher votre album photo par signification ? Essayez des requêtes telles que &quot;montrez-moi mes photos où je porte une veste bleue et suis assis sur un banc&quot;, &quot;montrez-moi des photos du mont Everest&quot; ou &quot;saké et sushi&quot;. 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&#x27;application peut comprendre et rechercher différents types d&#x27;entrées - texte, images et audio - et pas seulement des mots. Hybride signifie qu&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" style="width: 100%;" /><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&#x27;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&#x27;image à image. Une interface utilisateur Streamlit donne vie à ce projet en nous fournissant un frontend qui nous permet non seulement d&#x27;effectuer une recherche textuelle pour trouver et afficher les photos correspondantes de l&#x27;album, mais aussi d&#x27;identifier le sommet de la montagne à partir de l&#x27;image téléchargée et d&#x27;afficher d&#x27;autres photos de cette montagne dans l&#x27;album photo.<br/>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&#x27;a demandé de lui montrer toutes les photos du mont Ama Dablam prises lors de mon trek au camp de base de l&#x27;Everest. En parcourant l&#x27;album photo, on m&#x27;a également demandé d&#x27;identifier plusieurs autres pics montagneux, dont certains que je n&#x27;arrivais pas à nommer.</p><p>Cela m&#x27;a donné l&#x27;idée d&#x27;un projet amusant de vision par ordinateur. Ce que nous voulions réaliser :</p><ul><li>trouver des images d&#x27;un sommet de montagne par son nom</li><li>deviner le nom du sommet d&#x27;une montagne à partir d&#x27;une image et trouver des sommets similaires dans l&#x27;album photo</li><li>faire fonctionner les requêtes conceptuelles<em>(personne</em>, <em>rivière</em>, <em>drapeaux de prière</em>, <em>etc.)</em></li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" style="width: 100%;" /><h2>Assembler l&#x27;é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 (&quot;Ama Dablam&quot;) et les images (photos de mon album) en vecteurs pouvant être comparés de manière significative, c&#x27;est-à-dire dans le même espace vectoriel. Une fois cette étape franchie, la recherche se résume à &quot;trouver les voisins les plus proches&quot;.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" style="width: 100%;" /><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p><a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a>, récemment publié par Google, s&#x27;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&#x27;utilisation : des photos non étiquetées et des pics avec des noms et des langues différents. Parce qu&#x27;il est formé à la correspondance texte ↔ image, une photo de montagne prise lors d&#x27;une randonnée et un court texte d&#x27;incitation se retrouvent proches en tant qu&#x27;ancrages, même lorsque la langue ou l&#x27;orthographe de la requête varient.</p><p>SigLIP-2 offre un excellent rapport qualité-vitesse, prend en charge plusieurs résolutions d&#x27;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&#x27;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&#x27;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&#x27;emblée le filtrage et le tri. Il est également évolutif horizontalement, ce qui permet de passer facilement d&#x27;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&#x27;intègre parfaitement au projet. Enfin, nous avons besoin d&#x27;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&#x27;images réactive et des menus déroulants pour le tri et la géolocalisation. Il est facile de le cloner et de l&#x27;exécuter localement, et il fonctionne également dans un cahier Colab.</p><h2>Implémentation</h2><h3>Conception et stratégie d&#x27;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&#x27;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&#x27;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&#x27;index :</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Champ d'application</td><td style="border: 1px solid #ccc; padding: 4px;">Type</td><td style="border: 1px solid #ccc; padding: 4px;">Exemple</td><td style="border: 1px solid #ccc; padding: 4px;">Objectif/Notes</td><td style="border: 1px solid #ccc; padding: 4px;">Vecteur/Indexation</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">id</td><td style="border: 1px solid #ccc; padding: 4px;">mot-clé</td><td style="border: 1px solid #ccc; padding: 4px;">ama-dablam</td><td style="border: 1px solid #ccc; padding: 4px;">Slug/id stable</td><td style="border: 1px solid #ccc; padding: 4px;">-</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">noms</td><td style="border: 1px solid #ccc; padding: 4px;">texte + sous-champ mot-clé</td><td style="border: 1px solid #ccc; padding: 4px;">["Ama Dablam","Amadablam"]</td><td style="border: 1px solid #ccc; padding: 4px;">Alias / noms multilingues ; names.raw pour les filtres exacts</td><td style="border: 1px solid #ccc; padding: 4px;">-</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">latlon</td><td style="border: 1px solid #ccc; padding: 4px;">geo_point</td><td style="border: 1px solid #ccc; padding: 4px;">{"lat":27.8617,"lon":86.8614}</td><td style="border: 1px solid #ccc; padding: 4px;">Coordonnées GPS du pic sous la forme d'une combinaison latitude/longitude (facultatif)</td><td style="border: 1px solid #ccc; padding: 4px;">-</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">elev_m</td><td style="border: 1px solid #ccc; padding: 4px;">entier</td><td style="border: 1px solid #ccc; padding: 4px;">6812</td><td style="border: 1px solid #ccc; padding: 4px;">Élévation (facultatif)</td><td style="border: 1px solid #ccc; padding: 4px;">-</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">texte_embed</td><td style="border: 1px solid #ccc; padding: 4px;">dense_vector</td><td style="border: 1px solid #ccc; padding: 4px;">768</td><td style="border: 1px solid #ccc; padding: 4px;">Prototype mixte (invites et éventuellement 1 à 3 images de référence) pour ce pic</td><td style="border: 1px solid #ccc; padding: 4px;">index:true, similarité :"cosine", index_options :{type:"hnsw", m:16, ef_construction:128}</td>
                    </tr>
              </tbody>
            </table>
          <p>Cet index est principalement utilisé pour des recherches d&#x27;image à image, telles que l&#x27;identification de sommets de montagne à partir d&#x27;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 &quot;Quelle est cette montagne ?&quot; en un problème ciblé de plus proche voisin, séparant efficacement la compréhension conceptuelle des complexités des données d&#x27;image.</p><p><strong>Stratégie d&#x27;indexation pour l&#x27;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&#x27;é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&#x27;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>Créer un prototype de texte en utilisant :<ul><li>noms des sommets</li><li>l&#x27;<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 :<ul><li>&quot;photo naturelle du sommet de la montagne {name} dans l&#x27;Himalaya, Népal&quot;.</li><li>&quot;{name} sommet emblématique de la région du Khumbu, paysage alpin&quot;</li><li>&quot;{name} sommet montagneux, neige, ligne de crête rocheuse&quot;</li></ul></li><li><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&#x27;il ne faut pas faire) : soustraire un petit vecteur pour &quot;peinture, illustration, affiche, carte, logo&quot; afin de privilégier les photos réelles.</li></ul></li><li><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&#x27;image</a> si des images de référence du pic sont fournies.</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&#x27;image</a> pour générer l&#x27;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><pre><code>def l2norm(v: np.ndarray) -> 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,
) -> 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 (<= 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 > 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]</code></pre><p>Exemple de document provenant de l&#x27;index <code>peaks_catalog</code>:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" style="width: 100%;" /><h4>Index des photos</h4><p>Cet index primaire contient des informations détaillées sur toutes les photos de l&#x27;album. Chaque document représente une seule photo, contenant les informations suivantes :</p><ul><li>Chemin d&#x27;accès relatif à la photo dans l&#x27;album photo. Cette option permet de visualiser l&#x27;image correspondante ou de charger l&#x27;image dans l&#x27;interface de recherche.</li><li>Informations sur le GPS et l&#x27;heure de la photo.</li><li>Vecteur dense pour le codage d&#x27;images généré par SigLIP-2.</li><li><code>predicted_peaks</code> qui nous permet de filtrer par nom de pic.<br/><br/><strong>Cartographie de l&#x27;index</strong></li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Champ d'application</td><td style="border: 1px solid #ccc; padding: 4px;">Type</td><td style="border: 1px solid #ccc; padding: 4px;">Exemple</td><td style="border: 1px solid #ccc; padding: 4px;">Objectif/Notes</td><td style="border: 1px solid #ccc; padding: 4px;">Vecteur / Indexation</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">chemin</td><td style="border: 1px solid #ccc; padding: 4px;">mot-clé</td><td style="border: 1px solid #ccc; padding: 4px;">data/images/IMG_1234.HEIC</td><td style="border: 1px solid #ccc; padding: 4px;">Comment l'interface utilisateur ouvre la vignette/l'image complète</td><td style="border: 1px solid #ccc; padding: 4px;">-</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">clip_image</td><td style="border: 1px solid #ccc; padding: 4px;">dense_vector</td><td style="border: 1px solid #ccc; padding: 4px;">768</td><td style="border: 1px solid #ccc; padding: 4px;">Intégration d'images SigLIP-2</td><td style="border: 1px solid #ccc; padding: 4px;">index:true, similarité :"cosine", index_options :{type:"hnsw", m:16, ef_construction:128}</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">pics_prédits</td><td style="border: 1px solid #ccc; padding: 4px;">mot-clé</td><td style="border: 1px solid #ccc; padding: 4px;">["ama-dablam","pumori"]</td><td style="border: 1px solid #ccc; padding: 4px;">Les suppositions Top-K au moment de l'indexation (filtre / facette UX bon marché)</td><td style="border: 1px solid #ccc; padding: 4px;">-</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">GPS</td><td style="border: 1px solid #ccc; padding: 4px;">geo_point</td><td style="border: 1px solid #ccc; padding: 4px;">{"lat":27.96,"lon":86.83}</td><td style="border: 1px solid #ccc; padding: 4px;">permet d'utiliser des filtres géographiques</td><td style="border: 1px solid #ccc; padding: 4px;">-</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">heure de la prise de vue</td><td style="border: 1px solid #ccc; padding: 4px;">date</td><td style="border: 1px solid #ccc; padding: 4px;">2023-10-18T09:41:00Z</td><td style="border: 1px solid #ccc; padding: 4px;">temps de capture : tri/filtre</td><td style="border: 1px solid #ccc; padding: 4px;">-</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>Stratégie d&#x27;indexation pour l&#x27;index des photos : </strong>Pour chaque photo de l&#x27;album, nous procédons comme suit :<br/>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&#x27;image</a>.</p><ul><li><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">Intégration d&#x27;images SigLIP-2</a>: passage de l&#x27;image dans le modèle et normalisation L2 du vecteur. Stocker l&#x27;intégration dans le champ <code>clip_image</code>.</li><li><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&#x27;abord le vecteur image de la photo généré à l&#x27;étape précédente, puis nous effectuons une recherche rapide par kNN sur le champ text_embed de l&#x27;index <code>peaks_catalog</code>. Nous conservons les 3-4 premiers sommets et ignorons les autres.</li><li>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&#x27;image et du chemin d&#x27;accès. Cela permet de s&#x27;assurer qu&#x27;il n&#x27;y a pas de doublons après plusieurs exécutions.</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&#x27;aide de l&#x27;indexation <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">en bloc :</a></p><pre><code>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",
) -> 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) >= 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")</code></pre><p>Exemple de document de l&#x27;index des photos :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" style="width: 100%;" /><p>En résumé, l&#x27;index des photos est le magasin rapide, filtrable et prêt pour le kNN de toutes les photos de l&#x27;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&#x27;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&#x27;album photo sous forme de points verts et les pics montagneux de l&#x27;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&#x27;Everest.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" style="width: 100%;" /><h2>Cas d&#x27;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 &quot;drapeaux de prière&quot;) à l&#x27;aide de requêtes textuelles. Pour ce faire, l&#x27;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&#x27;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&#x27;enchâssements de texte dans l&#x27;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&#x27;</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&#x27;invites</a>, soustraction d&#x27;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&#x27;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&#x27;exclure les sommets qui ressemblent à d&#x27;autres et qui ne sont pas visibles lors de la randonnée.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" style="width: 100%;" /><p><strong>Requête Elasticsearch avec filtre géographique :</strong></p><pre><code>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"
       }
     }
}</code></pre><p><strong>Recherche par image (image à image) :</strong> Cette fonction permet d&#x27;identifier une montagne sur une photo et de trouver d&#x27;autres images de cette même montagne dans l&#x27;album photo. Lorsqu&#x27;une image est téléchargée, elle est traitée par l&#x27;encodeur d&#x27;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&#x27;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&#x27;index des photos pour localiser les images correspondantes.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" style="width: 100%;" /><p><strong>Requête Elasticsearch :</strong></p><p>Étape 1 : Trouver les noms de pics correspondants</p><pre><code>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...
         ]
       }
     }
   ]
 }
}</code></pre><p>Étape 2 : Effectuer une recherche dans l&#x27;index <code>photos</code> pour trouver les images correspondantes (même requête que dans le cas d&#x27;utilisation de la recherche texte-image) :</p><pre><code>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"
       }
     }
}</code></pre><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&#x27;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&#x27;<strong>identification à partir du</strong> téléchargement d&#x27;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 &quot; <strong>Voir l&#x27;image&quot;</strong> qui permet d&#x27;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&#x27;album photo.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" style="width: 100%;" /><p><strong>Recherche par texte</strong>: Trouver les sommets correspondants dans l&#x27;album à partir d&#x27;un texte.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" style="width: 100%;" /><h2>Conclusion</h2><p>Tout a commencé par <em>la possibilité de voir les  photos de<strong>l&#x27;Ama Dablam.</strong></em> s&#x27;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&#x27;identification) et un index évolutif <code>photos</code> de vecteurs d&#x27;images et d&#x27;EXIF (pour la recherche). Il est pratique, reproductible et facile à étendre.</p><p>Si vous souhaitez l&#x27;accorder, vous pouvez jouer avec quelques paramètres :</p><ul><li><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></li><li><strong>Paramètres de temps d&#x27;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.</li></ul><p>A l&#x27;avenir, des modèles natifs/rerankers pour la recherche <strong>multimodale</strong> et <strong>multilingue</strong> seront bientôt intégrés à l&#x27;écosystème Elastic, ce qui devrait rendre la recherche d&#x27;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><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></li><li><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></li></ul><p>Notre voyage s&#x27;achève donc et il est temps de prendre l&#x27;avion du retour. J&#x27;espère que cela vous a été utile et si vous le cassez (ou l&#x27;améliorez), j&#x27;aimerais savoir ce que vous avez changé.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" style="width: 100%;" />]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/multilingual-embedding-model-hybrid-search-reranking</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/multilingual-embedding-model-hybrid-search-reranking</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
            <pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d341b04acecc8eeec321f5404e1643447ecc8526-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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&#x27;Elastic (ainsi que d&#x27;autres modèles d&#x27;intégration de texte multilingue de Hugging Face) et nous nous sommes plongés dans la génération d&#x27;intégrations vectorielles denses à partir de vos données textuelles à l&#x27;aide d&#x27;Elasticsearch et de Kibana. Dans ce blog, nous examinerons les résultats de ces encastrements et mettrons en évidence les avantages significatifs de l&#x27;utilisation d&#x27;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 &quot;en&quot; pour référence :</p><pre><code># 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": {...}
       }
     },
     . . .</code></pre><h2>Effectuer une recherche en anglais</h2><p>Essayons d&#x27;effectuer la recherche en anglais et voyons ce qu&#x27;il en est :</p><pre><code>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"
    }
  }
}
}</code></pre><pre><code>{
       "_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"
       }
     },
     . . .</code></pre><p>Ici, même si la requête semble faussement simple, nous recherchons les enchâssements numériques du mot &quot;kitty&quot; 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&#x27;être liés à &quot;kitty&quot; : &quot;chat&quot;, &quot;chaton&quot;, &quot;félin&quot;, &quot;gatto&quot; (italien), &quot;mèo&quot; (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&#x27;un chat l<code>ying on something</code> donne des documents en italien, en néerlandais ou en vietnamien. Une question d&#x27;efficacité !</p><h2>Recherche de contenu dans d&#x27;autres langues</h2><pre><code>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"
     }
   }
 }
}</code></pre><pre><code>{
 "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"
}
. . .</code></pre><p>De même, une recherche par mot-clé pour &quot;chat&quot; en coréen (&quot;고양이&quot;) donnera également des résultats significatifs. Ce qui est spectaculaire ici, c&#x27;est que nous n&#x27;avons même pas de documents en coréen dans cet index !</p><pre><code>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: 고양이"
     }
   }
 }
}</code></pre><pre><code> {
       {
         "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"
       }
     },</code></pre><p>Cela fonctionne parce que le modèle d&#x27;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 &quot;quelle est la couleur du chat ?&quot; en vietnamien donnera un grand nombre de résultats, mais les 1 ou 2 premiers ne seront pas forcément les plus pertinents.</p><pre><code>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ì?"
     }
   }
 }
}</code></pre><p>Les résultats mentionnent tous le chat, ou une forme de couleur :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/33e1e887dbbdd1066cfedc7375f5e3b46538529e-859x847.png" style="width: 100%;" /><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><pre><code>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ì?"
  }
}
}</code></pre><pre><code> {
       "_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"
       }
     },</code></pre><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&#x27;est que notre recherche vectorielle a détecté une omission dans la légende anglaise de l&#x27;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&#x27;est la force de la recherche vectorielle.</p><h2>Conclusion</h2><p>Dans ce blog, nous avons présenté l&#x27;utilité d&#x27;un modèle d&#x27;intégration multilingue, et comment tirer parti d&#x27;Elasticsearch pour intégrer les modèles afin de générer des intégrations, et d&#x27;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&#x27;emploi sur la langue et l&#x27;ensemble de données de votre choix.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/multilingual-embedding-model-deployment-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/multilingual-embedding-model-deployment-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
            <pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f2aa7bebb6b2f701e274ba7282273f9ab4abed6-720x432.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Introduction</h2><p>Dans un monde d&#x27;utilisateurs globaux, la recherche d&#x27;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&#x27;importe quelle</em> langue, ce qui améliore l&#x27;expérience de l&#x27;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&#x27;il soit nécessaire de localiser les données à l&#x27;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&#x27;intégration de textes multilingues nous permettent justement de le faire. Les emboîtements sont un moyen de représenter le sens d&#x27;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&#x27;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&#x27;aide de techniques telles que l&#x27;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&#x27;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&#x27;entraînement comprennent des paires de textes dans différentes langues qui sont des traductions l&#x27;une de l&#x27;autre, ce qui permet au modèle d&#x27;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><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.</li><li><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.</li><li><strong>Pertinence</strong>: Fournit des résultats plus pertinents en se concentrant sur la similarité conceptuelle entre les requêtes et les documents.</li></ul><p>&quot;Prenons l&#x27;exemple d&#x27;un chercheur universitaire qui étudie l&#x27;impact des médias sociaux sur le discours politique&quot; dans différents pays. Grâce à la recherche vectorielle, ils peuvent saisir des requêtes telles que &quot;l&#x27;impatto dei social media sul discorso politico&quot; (italien) ou &quot;ảnh hưởng của mạng xã hội đối với diễn ngôn chính trị&quot; (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&#x27;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&#x27;é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&#x27;<a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">ensemble de données multilingues COCO</a>, qui contient des légendes d&#x27;images dans plusieurs langues, pour nous aider à visualiser deux types de recherches :</p><ol><li>Requêtes et termes de recherche dans d&#x27;autres langues sur un ensemble de données en anglais, et</li><li>Requêtes en plusieurs langues à partir d&#x27;un ensemble de données contenant des documents en plusieurs langues.</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>Python 3.6+</li><li>Elasticsearch 8+</li><li>Client Elasticsearch Python : pip install elasticsearch</li></ul><h2>Ensemble de données</h2><p>L&#x27;<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&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d4f0632529c71a22fbdecf21c9f4f0bb64b8e69c-1600x567.png" style="width: 100%;" /><h3>Étape 1 : télécharger l&#x27;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&#x27;aide d&#x27;un simple appel à l&#x27;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><pre><code>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&config=default&split=restval&offset=0&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)</code></pre><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><pre><code>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)</code></pre><p>c) Données d&#x27;index</p><pre><code># 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!")</code></pre><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&#x27;on peut utiliser dès sa sortie de l&#x27;emballage. En cliquant sur &quot;Déployer&quot;, 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&#x27;utilisation.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/95cd8f352425d1db2d04b00c3c88d1e71d1ef19a-1600x440.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1599d99949dda8267acc58f400a403a3af5373ef-1600x655.png" style="width: 100%;" /><p>Si vous souhaitez utiliser d&#x27;autres modèles d&#x27;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&#x27;Elastic</a> pour importer le modèle de HuggingFace.</p><pre><code>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</code></pre><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&#x27;enchâssements pour les données d&#x27;origine avec le modèle déployé</h3><p>Pour créer les enchâssements, nous devons d&#x27;abord créer un pipeline d&#x27;ingestion qui nous permettra de prendre le texte et de le faire passer par le modèle d&#x27;enchâssement de texte d&#x27;inférence. Vous pouvez le faire dans l&#x27;interface utilisateur de Kibana ou via l&#x27;API d&#x27;Elasticsearch.</p><p><strong>Pour ce faire via l&#x27;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&#x27;index <code>coco</code><strong> </strong> , définissez Data view sur la vue de données coco nouvellement créée, et définissez Field sur <code>description</code> car c&#x27;est le champ pour lequel nous voulons générer des embeddings.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cfe189e13dc118d325e7fb90bdace0c912e29f51-1088x1600.png" style="width: 100%;" /><p>Cela fonctionne très bien ! Nous pouvons maintenant créer le pipeline d&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1e043c1c3279b66fbdf19c06b41e76e613043998-1600x1126.png" style="width: 100%;" /><p>L&#x27;assistant peut également remplir automatiquement les processeurs nécessaires pour gérer les défaillances lors de l&#x27;ingestion et du traitement des données.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8804d31b4f869078e3b2245040bbb0ab1720a94a-1600x1084.png" style="width: 100%;" /><p>Créons maintenant le pipeline d&#x27;ingestion. Je nomme le pipeline <code>coco_e5</code>. Une fois le pipeline créé avec succès, vous pouvez immédiatement l&#x27;utiliser pour générer les embeddings en réindexant les données indexées d&#x27;origine vers un nouvel index dans l&#x27;assistant. Cliquez sur <strong>Réindexer </strong>pour lancer le processus.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e34b1b18f5b24420d4581fe4d657c569926c2023-1600x1126.png" style="width: 100%;" /><h2>Pour des configurations plus complexes, nous pouvons utiliser l&#x27;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&#x27;ajouter certains textes à l&#x27;entrée réelle avant de générer les enchâssements, faute de quoi les performances s&#x27;en trouveront dégradées.</p><p>Par exemple, avec le e5, le modèle s&#x27;attend à ce que le texte d&#x27;entrée suive &quot;passage : {content of passage}&quot;. Utilisons les pipelines d&#x27;ingestion pour y parvenir : Nous allons créer un nouveau pipeline d&#x27;ingestion <strong>vectorize_descriptions</strong>. Dans ce pipeline, nous allons créer un nouveau champ temporaire <code>temp_desc</code>, ajouter &quot;passage : &quot; 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><pre><code>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"
   }
 }
]
}</code></pre><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&#x27;un seul bit. Cela permet de réduire l&#x27;empreinte mémoire de 96% (ou 32x) au prix d&#x27;une plus grande précision. J&#x27;opte pour ce type de quantification parce que je sais que j&#x27;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&#x27;<strong>index_options</strong>est <strong>bbq_hnsw</strong>.</p><pre><code>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" 
       }
     }
   }
 }
}</code></pre><p>Nous pouvons maintenant réindexer les documents originaux dans un nouvel index, avec notre pipeline d&#x27;ingestion qui va &quot;vectoriser&quot; ou créer des embeddings pour le champ des descriptions.</p><pre><code>POST _reindex?wait_for_completion=false
{
 "source": {
   "index": "coco"
 },
 "dest": {
   "index": "coco_multilingual",
   "pipeline": "vectorize_descriptions"
 }
}</code></pre><p>Et c&#x27;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&#x27;interface utilisateur Kibana, soit avec l&#x27;API Elasticsearch. Dans la deuxième partie de cette série, nous explorerons les résultats et les nuances de l&#x27;utilisation d&#x27;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&#x27;emploi sur la langue et l&#x27;ensemble de données de votre choix.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Serveur MCP élastique : Expose les outils de l'Agent Builder à n'importe quel agent d'intelligence artificielle]]></title>
            <description><![CDATA[Découvrez comment utiliser le serveur Elastic MCP intégré dans Agent Builder pour étendre en toute sécurité n'importe quel agent d'IA avec un accès à vos données privées et à vos outils personnalisés.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elastic-mcp-server-agent-builder-tools</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elastic-mcp-server-agent-builder-tools</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jedr Blaszyk, Joe McElroy]]></dc:creator>
            <pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ef5153551a1d14833c7f512fede554d1dfb31553-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic Agent Builder est une plateforme permettant de créer des outils et des agents profondément intégrés à vos propres données dans Elasticsearch. Par exemple, vous pouvez créer des outils qui effectuent des recherches sémantiques sur des documents internes, analysent les journaux d&#x27;observabilité ou interrogent les alertes de sécurité.</p><p>Mais la véritable magie s&#x27;opère lorsque vous pouvez introduire ces outils personnalisés et conscients des données dans les environnements où vous passez le plus clair de votre temps. Et si l&#x27;agent de votre éditeur de code pouvait accéder en toute sécurité à la base de connaissances privée de votre organisation ?</p><p>C&#x27;est là qu&#x27;intervient le <strong>protocole de contexte de modèle (MCP).</strong> Elastic Agent Builder est livré avec un serveur MCP intégré qui permet d&#x27;accéder aux outils de la plateforme.</p><h2>Pourquoi utiliser le serveur MCP Elastic Agent Builder ?</h2><p>Les agents d&#x27;IA sont incroyablement puissants, mais leurs connaissances sont généralement limitées aux données sur lesquelles ils ont été formés et aux informations qu&#x27;ils peuvent activement rechercher sur l&#x27;internet public. Ils ne connaissent pas les documents de conception internes de votre entreprise, les manuels de déploiement spécifiques de votre équipe ou la structure unique des journaux de votre application.</p><p>Le défi consiste à donner à votre assistant d&#x27;intelligence artificielle le contexte spécialisé dont il a besoin. C&#x27;est précisément le problème que le MCP est censé résoudre. <strong>MCP est une norme ouverte qui permet à un modèle ou à un agent d&#x27;IA de découvrir et d&#x27;utiliser des outils externes.</strong></p><p>Pour rendre cela possible, l&#x27;Elastic Agent Builder expose nativement vos outils personnalisés par le biais d&#x27;un serveur MCP intégré. Cela signifie que vous pouvez facilement connecter n&#x27;importe quel client compatible MCP, comme <strong>Cursor</strong>, <strong>VS Code</strong> ou <strong>Claude Desktop</strong>, avec les outils spécialisés et sensibles aux données que vous avez construits avec Elastic Agent Builder.</p><h2>Quand utiliser MCP (et quand ne pas le faire)</h2><p>Elastic Agent Builder comprend plusieurs protocoles pour prendre en charge différents modèles d&#x27;intégration. Il est essentiel de choisir la bonne solution pour mettre en place des flux de travail efficaces en matière d&#x27;IA.</p><ul><li><strong>Utilisez <a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">MCP</a></strong> pour compléter votre agent d&#x27;intelligence artificielle (comme dans <strong>Cursor</strong> ou <strong>VS Code</strong>) avec des outils spécialisés. Il s&#x27;agit de l&#x27;approche &quot;&quot;apportez vos propres outils&quot;&quot;, qui permet d&#x27;améliorer l&#x27;assistant que vous utilisez déjà grâce à un accès sécurisé à vos données privées. Seuls les outils sont exposés par l&#x27;intermédiaire du serveur MCP - les agents d&#x27;Elastic sont distincts.</li><li><strong>Utilisez le <a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server">protocole A2A</a></strong> pour permettre à votre agent Elastic personnalisé de collaborer avec d&#x27;autres agents autonomes (comme dans <a href="https://www.elastic.co/search-labs/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise"><strong>Gemini Enterprise de Google)</strong></a>. Il s&#x27;agit de la délégation d&#x27;agent à agent, où chaque agent travaille comme un pair pour résoudre un problème.</li><li><strong>Utilisez les <a href="https://www.elastic.co/docs/solutions/search/agent-builder/kibana-api">API de l&#x27;Agent Builder</a></strong> pour un contrôle programmatique complet lors de la création d&#x27;une application personnalisée à partir de zéro.</li></ul><p>Pour un développeur qui cherche à obtenir des réponses dans sa documentation interne sans quitter son IDE, le MCP est la solution idéale.</p><h2>Exemple : vos outils personnalisés dans Cursor avec le serveur MCP Agent Builder</h2><p>Prenons un exemple pratique que j&#x27;utilise quotidiennement. Tout d&#x27;abord, j&#x27;ai exploré et indexé notre documentation technique interne dans un index Elasticsearch appelé <code>elastic-dev-docs</code>. Nous pourrions utiliser les outils génériques intégrés disponibles dans Agent Builder, mais nous allons créer notre propre outil personnalisé pour interroger cette base de connaissances spécifique.</p><p>La raison de la construction d&#x27;un outil sur mesure est simple : <strong>contrôle et précision</strong>. Cette approche nous permet d&#x27;exécuter une requête sémantique rapide directement sur notre index <code>elastic-dev-docs</code>. Nous avons un contrôle total sur l&#x27;index ciblé et sur la manière dont les données sont extraites.</p><p>Voici maintenant comment nous pouvons utiliser cette base de connaissances personnalisée dans un éditeur de code doté d&#x27;une intelligence artificielle, comme Cursor.</p><h3>Étape 1 : Créer un outil de base de connaissances personnalisé dans Agent Builder</h3><p>Tout d&#x27;abord, créez un nouvel outil dans Agent Builder. Une description claire et précise de l&#x27;outil est importante car c&#x27;est ainsi que tout agent d&#x27;IA, qu&#x27;il s&#x27;agisse de l&#x27;agent Elastic interne ou d&#x27;un outil externe tel que Cursor se connectant via MCP, découvre et sélectionne votre outil pour la bonne tâche.</p><p>Une description solide doit être explicite. Par exemple : &quot;Effectue une recherche sémantique sur l&#x27;index elastic-dev-docs pour trouver la documentation d&#x27;ingénierie interne, les runbooks et les procédures de mise en production.&quot;</p><p>L&#x27;outil est alors configuré pour effectuer une recherche sémantique dans notre index spécifique. Une fois sauvegardé, il est immédiatement disponible pour être servi.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1eea079908fdf7cc72dbe81abd07ff51601a43d4-1472x1600.png" style="width: 100%;" /><p>Avant de le connecter au monde extérieur, vous pouvez le tester directement dans l&#x27;interface utilisateur. Il suffit de cliquer sur le bouton <strong>Test</strong> pour remplir manuellement les paramètres, émuler ce que le LLM fera, et inspecter les résultats pour confirmer que tout fonctionne correctement.</p><h3>Étape 2 : Connecter le curseur au serveur Elastic MCP</h3><p>Elastic Agent Builder expose automatiquement tous les outils disponibles via un point de terminaison MCP sécurisé. Vous pouvez trouver l&#x27;URL unique de votre serveur dans l&#x27;interface utilisateur Outils de Kibana.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ba137be30f0eaa7f028b96bd8af4e2779c3f8a33-1600x589.png" style="width: 100%;" /><p>Pour se connecter à Cursor, il suffit d&#x27;ajouter cette URL à son fichier de configuration, ainsi qu&#x27;une clé API Elastic pour l&#x27;authentification<a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">(découvrez comment créer une clé API ES</a>). Nous utilisons une clé API pour l&#x27;autorisation, car elle garantit que les outils ne s&#x27;exécutent qu&#x27;avec les permissions que vous avez accordées, en respectant toutes vos règles de contrôle d&#x27;accès.</p><p>La configuration MCP dans le site <code>~/.cursor/mcp.json</code> de Cursor se présente comme suit :</p><pre><code>{
  "mcpServers": {
    "elastic-agent-builder": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://your-kibana.kb.company.io/api/agent_builder/mcp",
        "--header",
        "Authorization:${AUTH_HEADER}"
      ],
      "env": {
        "AUTH_HEADER": "ApiKey <ELASTIC_API_KEY>"
      }
    }
  }
}</code></pre><p>Une fois la configuration sauvegardée, vous devriez voir l&#x27;outil Elastic Agent Builder MCP server disponible dans Cursor.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d302c6d3609fbf14fd40e21b9e69e567bf12553f-1600x1002.png" style="width: 100%;" /><h3>Étape 3 : Posez vos questions !</h3><p>Une fois la connexion établie, les agents Cursor peuvent maintenant invoquer vos outils personnalisés pour répondre à vos questions ou guider le processus de génération de code.</p><p>Posons une question précise :</p><p><em>&quot;Consulter les étapes pour libérer le service de crawler de la documentation interne de l&#x27;ingénierie d&#x27;elastic search org&quot;</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/14f572730203c23615bb9dd38234bcb3b0f81155-1600x1468.png" style="width: 100%;" /><p>En coulisses, la magie opère :</p><ol><li>L&#x27;agent du curseur décide de la meilleure façon de répondre à votre question et décide d&#x27;appeler la fonction <code>engineering_documentation_internal_search</code></li><li>Il invoque l&#x27;outil à l&#x27;aide d&#x27;une requête en langage naturel</li><li>L&#x27;outil effectue une recherche sémantique dans l&#x27;index <code>elastic-dev-docs</code> et renvoie les procédures les plus pertinentes et les plus récentes.</li></ol><p>Nous obtenons une réponse précise et fiable, basée sur notre documentation interne, sans jamais quitter l&#x27;éditeur de code. L&#x27;expérience est transparente et puissante.</p><h2>À vous de construire</h2><p>Vous avez maintenant vu comment utiliser le serveur MCP intégré dans Elastic Agent Builder pour étendre vos assistants IA avec un accès sécurisé à vos données privées. Pour que les modèles soient réellement utiles, il est essentiel de les ancrer dans vos propres informations.</p><p>Pour récapituler, nous avons couvert les principales étapes :</p><ul><li>Choisir le bon protocole pour vos besoins (MCP).</li><li>Création d&#x27;un outil de base de connaissances personnalisé.</li><li>Connecter cet outil à un assistant IDE comme Cursor.</li></ul><p>Vos agents et vos outils n&#x27;ont plus besoin d&#x27;être déconnectés de leur contexte le plus précieux. Nous espérons que ce guide vous aidera à créer des flux de travail plus efficaces et tenant compte des données. Bonne construction !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Formation de modèles LTR dans Elasticsearch avec des listes de jugement basées sur les données de comportement des utilisateurs]]></title>
            <description><![CDATA[Apprenez à utiliser les données UBI pour créer des listes de jugement afin d'automatiser la formation de vos modèles LTR (Learning to Rank) dans Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/training-learning-to-rank-models-elasticsearch-ubi-data</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/training-learning-to-rank-models-elasticsearch-ubi-data</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
            <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/762bf09c28829d626d42c2cfadc719e1dd618d1b-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>L&#x27;une des principales difficultés rencontrées lors de l&#x27;utilisation de modèles d&#x27;<a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr"><strong><em>apprentissage par classement</em></strong></a> est la création d&#x27;une <a href="https://www.elastic.co/search-labs/blog/judgment-lists"><strong><em>liste de jugements de</em></strong></a> haute qualité pour entraîner le modèle. Traditionnellement, ce processus implique une évaluation <strong><em>manuelle</em></strong> de la pertinence des requêtes et des documents afin d&#x27;attribuer une note à chacun d&#x27;entre eux. Il s&#x27;agit d&#x27;un processus lent, peu évolutif et difficile à maintenir (imaginez que vous deviez mettre à jour à la main une liste comportant des centaines d&#x27;entrées).</p><p>Et si nous pouvions utiliser les interactions réelles des utilisateurs avec notre application de recherche pour créer ces données d&#x27;entraînement ? L&#x27;utilisation des données <a href="https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-insights"><strong><em>UBI</em></strong></a> nous permet justement de le faire. Créer un système automatique capable de capturer et d&#x27;utiliser nos recherches, nos clics et autres interactions pour générer une liste de jugement. Ce processus peut être mis à l&#x27;échelle et répété beaucoup plus facilement qu&#x27;une interaction manuelle et tendrait à donner de meilleurs résultats. Dans ce blog, nous allons explorer comment nous pouvons interroger les données UBI stockées dans Elasticsearch pour calculer des signaux significatifs afin de générer un ensemble de données d&#x27;entraînement pour un modèle <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction"><strong><em>LTR</em></strong></a>.</p><p><strong><em>L&#x27;expérience complète est disponible <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git">ici</a>.</em></strong></p><h2>Pourquoi les données UBI peuvent-elles être utiles pour entraîner votre modèle LTR ?</h2><p>Les données UBI offrent plusieurs avantages par rapport à une annotation manuelle :</p><ul><li><strong>Le volume :</strong> Étant donné que les données UBI proviennent d&#x27;interactions réelles, nous pouvons collecter beaucoup plus de données que nous ne pouvons en générer manuellement. Cela suppose que nous ayons suffisamment de trafic pour générer ces données, bien entendu.</li><li><strong>L&#x27;intention de l&#x27;utilisateur réel :</strong> Traditionnellement, une liste de jugement manuel provient d&#x27;une évaluation experte des données disponibles. D&#x27;autre part, les données UBI reflètent le comportement réel des utilisateurs. Cela signifie que nous pouvons générer de meilleures données de formation qui amélioreront la précision de notre système de recherche, car elles sont basées sur la façon dont les utilisateurs interagissent réellement avec votre contenu et y trouvent de la valeur, plutôt que sur des hypothèses théoriques sur ce qui devrait être pertinent.</li><li><strong>Mises à jour permanentes :</strong> Les listes de jugement doivent être actualisées au fil du temps. Si nous les créons à partir des données de l&#x27;UBI, nous pouvons disposer de données actualisées qui se traduisent par des listes de jugement mises à jour.</li><li><strong>Rentabilité :</strong> Sans les frais généraux liés à la création manuelle d&#x27;une liste de jugement, le processus peut être répété efficacement un nombre illimité de fois.</li><li><strong>Distribution naturelle des requêtes</strong>: Les données UBI représentent les requêtes réelles des utilisateurs, ce qui peut entraîner des changements plus profonds. Par exemple, nos utilisateurs utilisent-ils le langage naturel pour effectuer des recherches dans notre système ? Si c&#x27;est le cas, nous pourrions vouloir mettre en œuvre une recherche sémantique ou une approche de recherche hybride.</li></ul><p>Elle s&#x27;accompagne toutefois de quelques mises en garde :</p><ul><li><strong>Amplification des biais : </strong>Un contenu populaire est plus susceptible de recevoir des clics, simplement parce qu&#x27;il est plus exposé. Cela pourrait donc avoir pour effet d&#x27;amplifier les articles les plus populaires et d&#x27;étouffer les meilleures options.</li><li><strong>Couverture incomplète : </strong>Le nouveau contenu n&#x27;a pas d&#x27;interactions, il peut donc être difficile pour lui de figurer en bonne place dans les résultats. Les requêtes rares peuvent également ne pas comporter suffisamment de points de données pour créer des données de formation significatives.</li><li><strong>Variations saisonnières :</strong> Si vous vous attendez à ce que le comportement de l&#x27;utilisateur change radicalement au fil du temps, les données historiques risquent de ne pas vous donner beaucoup d&#x27;indications sur ce qui constitue un bon résultat.</li><li><strong>Ambiguïté de la tâche :</strong> Un clic ne garantit pas toujours que l&#x27;utilisateur a trouvé ce qu&#x27;il cherchait.</li></ul><h2>Calcul des notes</h2><h3>Grades pour la formation LTR</h3><p>Pour former les modèles LTR, nous devons fournir une représentation numérique de la pertinence d&#x27;un document par rapport à une requête. Dans notre application, ce nombre est un score continu allant de 0,0 à 5,0+, les scores les plus élevés indiquant une plus grande pertinence.</p><p>Pour illustrer le fonctionnement de ce système de classement, prenons l&#x27;exemple suivant, créé manuellement :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Requête</td><td style="border: 1px solid #ccc; padding: 4px;">Contenu du document</td><td style="border: 1px solid #ccc; padding: 4px;">Grade</td><td style="border: 1px solid #ccc; padding: 4px;">Explication</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">"meilleure recette de pizza"</td><td style="border: 1px solid #ccc; padding: 4px;">"Recette de pâte à pizza italienne authentique avec photos étape par étape"</td><td style="border: 1px solid #ccc; padding: 4px;">4.0</td><td style="border: 1px solid #ccc; padding: 4px;">Très pertinent, exactement ce que l'utilisateur recherche</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">"meilleure recette de pizza"</td><td style="border: 1px solid #ccc; padding: 4px;">"Histoire de la pizza en Italie"</td><td style="border: 1px solid #ccc; padding: 4px;">1.0</td><td style="border: 1px solid #ccc; padding: 4px;">Un peu dans le sujet, il s'agit de pizza mais ce n'est pas une recette.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">"meilleure recette de pizza"</td><td style="border: 1px solid #ccc; padding: 4px;">"Recette de pizza en 15 minutes pour les débutants"</td><td style="border: 1px solid #ccc; padding: 4px;">3.0</td><td style="border: 1px solid #ccc; padding: 4px;">C'est un bon résultat, mais il ne s'agit peut-être pas de la "meilleure" recette.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">"meilleure recette de pizza"</td><td style="border: 1px solid #ccc; padding: 4px;">"Guide d'entretien des voitures"</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">Pas du tout pertinent, sans aucun rapport avec la requête</td>
                    </tr>
              </tbody>
            </table>
          <p>Comme nous pouvons le voir ici, la note est une représentation numérique de la pertinence d&#x27;un document par rapport à notre exemple de requête &quot;meilleure recette de pizza&quot;. Grâce à ces scores, notre modèle LTR peut apprendre quels documents devraient être présentés plus haut dans les résultats.</p><p>La manière de calculer les notes est au cœur de notre ensemble de données de formation. Il existe <a href="https://www.elastic.co/search-labs/blog/judgment-lists">plusieurs approches</a> pour y parvenir, chacune ayant ses propres forces et faiblesses. Par exemple, nous pourrions attribuer un score binaire de 1 pour pertinent, 0 pour non pertinent, ou nous pourrions simplement compter le nombre de clics dans un document résultant pour chaque requête.</p><p>Dans cet article de blog, nous utiliserons une approche différente, en <strong><em>prenant en compte le comportement de l&#x27;utilisateur en tant qu&#x27;entrée et en calculant un nombre de notes en tant que sortie.</em></strong> Nous corrigerons également le biais qui pourrait résulter du fait que les résultats les plus élevés ont tendance à être davantage cliqués, quelle que soit la pertinence du document.</p><h2>Calcul des notes - algorithme COEC</h2><p>L&#x27;algorithme COEC<a href="https://www.wsdm-conference.org/2010/proceedings/docs/p351.pdf">(Clicks over Expected Clicks</a>) est une méthodologie permettant de calculer les notes de jugement à partir des clics des utilisateurs.<br/>Comme nous l&#x27;avons indiqué précédemment, les utilisateurs ont tendance à cliquer sur les résultats les mieux positionnés, même si le document n&#x27;est pas le plus pertinent par rapport à la requête ; c&#x27;est ce que l&#x27;on appelle le <a href="https://eugeneyan.com/writing/position-bias/">biais de position.</a> L&#x27;idée centrale de l&#x27;utilisation de l&#x27;algorithme COEC est que tous les clics n&#x27;ont pas la même importance ; un clic sur un document en position 10 indique que ce document est beaucoup plus pertinent pour la requête qu&#x27;un clic sur un document en position 1. Pour citer le document de recherche sur l&#x27;algorithme COEC (lien ci-dessus) :</p><p><em>&quot;Il est bien connu que le taux de clics (CTR) des résultats de recherche ou des publicités diminue de manière significative en fonction de la position des résultats.&quot;</em></p><p>Pour en savoir plus sur les préjugés de position <a href="https://www.researchgate.net/publication/200110550_An_experimental_comparison_of_click_position-bias_models">, cliquez ici.</a></p><p>Pour résoudre ce problème avec l&#x27;algorithme COEC, nous suivons les étapes suivantes :</p><p><strong>1. Établir des valeurs de référence pour les positions :</strong> Nous calculons le taux de clics (CTR) pour chaque position de recherche de 1 à 10. Cela signifie que nous déterminons le pourcentage d&#x27;utilisateurs qui cliquent généralement sur la position 1, la position 2, etc. Cette étape permet de prendre en compte les préférences naturelles des utilisateurs en matière de position.<br/><br/>Nous calculons le CTR en utilisant :</p><div class="katex">CTRp=CpIp</div><p>Où ?</p><p>p = Position. De 1 à 10<br/>Cp = Nombre total de clics (sur n&#x27;importe quel document) à la position p sur l&#x27;ensemble des requêtes<br/>Ip = Nombre total d&#x27;impressions : Nombre de fois qu&#x27;un document est apparu à la position p sur l&#x27;ensemble des requêtes.</p><p>Dans ce cas, nous nous attendons à ce que les positions les plus élevées donnent lieu à davantage de clics.</p><p><strong>2.</strong> <strong>Calculer les clics attendus (EC)</strong>:</p><p>Cette mesure détermine le nombre de clics qu&#x27;un document &quot;aurait dû&quot; recevoir sur la base des positions dans lesquelles il est apparu et du CTR pour ces positions. Nous calculons la CE à l&#x27;aide de :</p><div class="katex">EC (for a document) = qQdCTRpos(d,q)</div><p>Où ?</p><p>Qd = Toutes les requêtes dans lesquelles le document d est apparu<br/>pos(d,q)= Position du document d dans les résultats de la requête q</p><p>3. <strong>Compter les clics réels : </strong>Nous comptons le nombre total de clics qu&#x27;un document a reçus pour toutes les requêtes où il est apparu, ci-après dénommé <strong>A(d).</strong></p><p>4. <strong>Calculer le score COEC :</strong> Il s&#x27;agit du rapport entre les clics réels (A(d)) et les clics attendus (EC(d)) :</p><div class="katex">COEC = A(d)EC(d)</div><p>Cette métrique normalise le biais de position de la manière suivante :</p><ul><li>Un score de 1,0 signifie que le document s&#x27;est comporté exactement comme prévu compte tenu des positions dans lesquelles il est apparu.</li><li>Un score supérieur à 1,0 signifie que le document a obtenu de meilleurs résultats que prévu en examinant ses positions. Ce document est donc plus pertinent pour la requête.</li><li>Un score inférieur à 1,0 signifie que le document a obtenu de moins bons résultats que prévu, compte tenu de ses positions. Ce document est donc moins pertinent pour la requête.</li></ul><p><strong><em>Le résultat final est une note qui reflète ce que les utilisateurs recherchent, en tenant compte des attentes basées sur la position, extraites des interactions réelles avec notre système de recherche.</em></strong></p><h2>Mise en œuvre technique</h2><p>Nous allons créer un script pour créer une liste de jugements afin d&#x27;entraîner un modèle LTR.</p><p>L&#x27;entrée de ce script est constituée des données UBI indexées dans Elastic (requêtes et événements).</p><p>Le résultat est une liste de jugements dans un fichier CSV généré à partir de ces documents UBI en utilisant l&#x27;algorithme COEC. Cette liste de jugements peut être utilisée avec <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction">Eland</a> pour extraire les caractéristiques pertinentes et former un modèle LTR.</p><h3>Démarrage rapide</h3><p>Pour générer une liste de jugement à partir de l&#x27;échantillon de données présenté dans ce blog, vous pouvez suivre les étapes suivantes :</p><p>1. Clonez le référentiel :</p><pre><code>git clone https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git  
cd elastic-ltr-judgement_list-blog</code></pre><p>2. Installer les bibliothèques nécessaires</p><p>Pour ce script, nous avons besoin des bibliothèques suivantes :</p><ul><li><em>pandas</em>: pour sauvegarder la liste des jugements</li><li><em>elasticsearch</em>: Pour obtenir les données UBI à partir de notre déploiement Elastic</li></ul><p>Nous avons également besoin de Python 3.11</p><pre><code>pip install -r requirements.txt</code></pre><p>3. Mettez à jour les variables d&#x27;environnement pour votre déploiement Elastic dans un <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/.env-example"> fichier .env</a></p><ul><li>ES_HOST</li><li>API_KEY</li></ul><p>Pour ajouter les variables d&#x27;environnement, utilisez :</p><pre><code>source .env</code></pre><p>4. Créez les index ubi_queries et ubi_events, et téléchargez les échantillons de données. Exécutez le fichier setup.py :</p><pre><code>python setup.py</code></pre><p>5. Exécutez le script Python :</p><pre><code>python judgement_list-generator.py</code></pre><p>Si vous suivez ces étapes, vous devriez voir apparaître un nouveau fichier appelé judgment_list.csv qui ressemble à ceci :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2531090131ac9fe3e4e1d79de9d156fc47a7825a-782x531.png" style="width: 100%;" /><p>Ce script calcule les notes en appliquant l&#x27;algorithme COEC évoqué précédemment à l&#x27;aide de la fonction <strong>calculate_relevance_grade()</strong> présentée ci-dessous.</p><h2>Architecture des données</h2><h3>Requêtes Ubi</h3><p>Notre index des requêtes UBI contient des informations sur les requêtes exécutées dans notre système de recherche. Il s&#x27;agit d&#x27;un document type :</p><pre><code>{
          "client_id": "client_002",
          "query": "italian pasta recipes",
          "query_attributes": {
            "search_type": "recipe",
            "category": "food",
            "cuisine": "italian"
          },
          "query_id": "q002",
          "query_response_id": "qr002",
          "query_response_object_ids": [
            "doc_011",
            "doc_012",
            "doc_013",
            "doc_014",
            "doc_015",
            "doc_016",
            "doc_017",
            "doc_018",
            "doc_019",
            "doc_020"
          ],
          "timestamp": "2024-08-14T11:15:00Z",
          "user_query": "italian pasta recipes"
        }</code></pre><p>Ici, nous pouvons voir les données de l&#x27;utilisateur (client_id), les résultats de la requête (query_response_object_ids) et la requête elle-même (timestamp, user_query).</p><h3>Événements de clics Ubi</h3><p>Notre index ubi_events contient des données à chaque fois qu&#x27;un utilisateur a cliqué sur un document dans les résultats. Il s&#x27;agit d&#x27;un document type :</p><pre><code>{
          "action_name": "click",
          "application": "recipe_search",
          "client_id": "client_001",
          "event_attributes": {
            "object": {
              "description": "Authentic Italian Pizza Dough Recipe with Step-by-Step Photos",
              "device": "desktop",
              "object_id": "doc_001",
              "position": {
                "ordinal": 1,
                "page_depth": 1
              },
              "user": {
                "city": "New York",
                "country": "USA",
                "ip": "192.168.1.100",
                "location": {
                  "lat": 40.7128,
                  "lon": -74.006
                },
                "region": "NY"
              }
            }
          },
          "message": "User clicked on document doc_001",
          "message_type": "click",
          "query_id": "q001",
          "timestamp": "2024-08-14T10:31:00Z",
          "user_query": "best pizza recipe"
        }</code></pre><h2>Script de génération de listes d&#x27;arrêts</h2><h3>Aperçu général du script</h3><p>Ce script automatise la génération de la liste de jugement en utilisant les données UBI des requêtes et des événements de clics stockés dans Elasticsearch. Il exécute ces tâches :</p><ul><li>Récupère et traite les données UBI dans Elasticsearch.</li><li>Corréler les événements UBI avec ses requêtes.</li><li>Calcule le CTR pour chaque position.</li><li>Calcule le nombre de clics attendus (EC) pour chaque document.</li><li>Compte les clics réels pour chaque document.</li><li>Calcule le score COEC pour chaque paire requête-document.</li><li>Génère une liste de jugements et l&#x27;écrit dans un fichier CSV.</li></ul><p>Passons en revue chaque fonction :</p><h3>connect_to_elasticsearch()</h3><pre><code>def connect_to_elasticsearch(host, api_key):
    """Create and return Elasticsearch client"""
    try:
        es = Elasticsearch(
            hosts=[host],
            api_key=api_key,
            request_timeout=60
        )
        # Test the connection
        if es.ping():
            print(f"✓ Successfully connected to Elasticsearch at {host}")
            return es
        else:
            print("✗ Failed to connect to Elasticsearch")
            return None
    except Exception as e:
        print(f"✗ Error connecting to Elasticsearch: {e}")
        return None</code></pre><p>Cette fonction renvoie un objet client Elasticsearch en utilisant l&#x27;hôte et la clé api.</p><h3>fetch_ubi_data()</h3><pre><code>def fetch_ubi_data(es_client: Elasticsearch, queries_index: str, events_index: str,
                   size: int = 10000) -> Tuple[List[Dict], List[Dict]]:
    """
    Fetch UBI queries and events data from Elasticsearch indices.

    Args:
        es_client: Elasticsearch client
        queries_index: Name of the UBI queries index
        events_index: Name of the UBI events index
        size: Maximum number of documents to fetch

    Returns:
        Tuple of (queries_data, events_data)
    """
    logger.info(f"Fetching data from {queries_index} and {events_index}")

    # Fetch queries with error handling
    try:
        queries_response = es_client.search(
            index=queries_index,
            body={
                "query": {"match_all": {}},
                "size": size
            }
        )
        queries_data = [hit['_source'] for hit in queries_response['hits']['hits']]
        logger.info(f"Fetched {len(queries_data)} queries")

    except Exception as e:
        logger.error(f"Error fetching queries from {queries_index}: {e}")
        raise

    # Fetch events (only click events for now) with error handling
    try:
        events_response = es_client.search(
            index=events_index,
            body={
                "query": {
                    "term": {"message_type.keyword": "CLICK_THROUGH"}
                },
                "size": size
            }
        )
        events_data = [hit['_source'] for hit in events_response['hits']['hits']]
        logger.info(f"Fetched {len(events_data)} click events")

    except Exception as e:
        logger.error(f"Error fetching events from {events_index}: {e}")
        raise

    logger.info(f"Data fetch completed successfully - Queries: {len(queries_data)}, Events: {len(events_data)}")

    return queries_data, events_data</code></pre><p>Cette fonction est la couche d&#x27;extraction des données ; elle se connecte à Elasticsearch pour récupérer les requêtes UBI à l&#x27;aide d&#x27;une requête match_all et filtre les événements UBI pour obtenir uniquement les événements &quot;CLICK_THROUGH&quot;.</p><h3>process_ubi_data()</h3><pre><code>def process_ubi_data(queries_data: List[Dict], events_data: List[Dict]) -> pd.DataFrame:
    """
    Process UBI data and generate judgment list.

    Args:
        queries_data: List of query documents from UBI queries index
        events_data: List of event documents from UBI events index

    Returns:
        DataFrame with judgment list (qid, docid, grade, keywords)
    """
    logger.info("Processing UBI data to generate judgment list")

    # Group events by query_id
    clicks_by_query = {}
    for event in events_data:
        query_id = event['query_id']
        if query_id not in clicks_by_query:
            clicks_by_query[query_id] = {}

        # Extract clicked document info
        object_id = event['event_attributes']['object']['object_id']
        position = event['event_attributes']['object']['position']['ordinal']

        clicks_by_query[query_id][object_id] = {
            'position': position,
            'timestamp': event['timestamp']
        }

    judgment_list = []

    # Process each query
    for query in queries_data:
        query_id = query['query_id']
        user_query = query['user_query']
        document_ids = query['query_response_object_ids']

        # Get clicks for this query
        query_clicks = clicks_by_query.get(query_id, {})

        # Generate judgment for each document shown
        for doc_id in document_ids:
            grade = calculate_relevance_grade(doc_id, query_clicks, document_ids, queries_data, events_data)

            judgment_list.append({
                'qid': query_id,
                'docid': doc_id,
                'grade': grade,
                'query': user_query
            })

    df = pd.DataFrame(judgment_list)
    logger.info(f"Generated {len(df)} judgment entries for {df['qid'].nunique()} unique queries")

    return df</code></pre><p>Cette fonction gère la génération de la liste des jugements. Il commence à traiter les données UBI en associant les événements UBI et les requêtes. Il appelle ensuite la fonction calculate_relevance_grade() pour chaque paire document-requête afin d&#x27;obtenir les entrées de la liste de jugement. Enfin, il renvoie la liste résultante sous la forme d&#x27;un cadre de données pandas.</p><h3>calculer_la_relevance_de_la_grade()</h3><pre><code>def calculate_relevance_grade(document_id: str, clicks_data: Dict,
                              query_response_ids: List[str], all_queries_data: List[Dict] = None,
                              all_events_data: List[Dict] = None) -> float:
    """
    Calculate COEC (Click Over Expected Clicks) relevance score for a document.

    Args:
        document_id: ID of the document
        clicks_data: Dictionary of clicked documents with their positions for current query
        query_response_ids: List of document IDs shown in search results (ordered by position)
        all_queries_data: All queries data for calculating position CTR averages
        all_events_data: All events data for calculating position CTR averages

    Returns:
        COEC relevance score (continuous value, typically 0.0 to 5.0+)
    """

    # If no global data provided, fall back to simple position-based grading
    if all_queries_data is None or all_events_data is None:
        logger.warning("No global data provided, falling back to position-based grading")
        # Simple fallback logic
        if document_id in clicks_data:
            position = clicks_data[document_id]['position']
            if position > 3:
                return 4.0
            elif position >= 1 and position <= 3:
                return 3.0
        if document_id in query_response_ids:
            position = query_response_ids.index(document_id) + 1
            if position <= 5:
                return 2.0
            elif position >= 6 and position <= 10:
                return 1.0
        return 0.0

    # Calculate rank-aggregated click-through rates
    position_ctr_averages = {}
    position_impression_counts = {}
    position_click_counts = {}

    # Initialize counters
    for pos in range(1, 11):  # Positions 1-10
        position_impression_counts[pos] = 0
        position_click_counts[pos] = 0

    # Count impressions (every document shown contributes)
    for query in all_queries_data:
        for i, doc_id in enumerate(query['query_response_object_ids'][:10]):  # Top 10 positions
            position = i + 1
            position_impression_counts[position] += 1

    # Count clicks by position
    for event in all_events_data:
        if event.get('action_name') == 'click':
            position = event['event_attributes']['object']['position']['ordinal']
            if position <= 10:
                position_click_counts[position] += 1

    # Calculate average CTR per position
    for pos in range(1, 11):
        if position_impression_counts[pos] > 0:
            position_ctr_averages[pos] = position_click_counts[pos] / position_impression_counts[pos]
        else:
            position_ctr_averages[pos] = 0.0

    # Calculate expected clicks for this specific document
    expected_clicks = 0.0

    # Count how many times this document appeared at each position for any query
    for query in all_queries_data:
        if document_id in query['query_response_object_ids']:
            position = query['query_response_object_ids'].index(document_id) + 1
            if position <= 10:
                expected_clicks += position_ctr_averages[position]

    # Count total actual clicks for this document across all queries
    actual_clicks = 0
    for event in all_events_data:
        if (event.get('action_name') == 'click' and
                event['event_attributes']['object']['object_id'] == document_id):
            actual_clicks += 1

    # Calculate COEC score
    if expected_clicks > 0:
        coec_score = actual_clicks / expected_clicks
    else:
        coec_score = 0.0

    logger.debug(
        f"Document {document_id}: {actual_clicks} clicks / {expected_clicks:.3f} expected = {coec_score:.3f} COEC")

    return coec_score</code></pre><p>Il s&#x27;agit de la fonction qui met en œuvre l&#x27;algorithme COEC. Il calcule le CTR pour chaque position, puis compare les clics réels pour une paire document-requête, et enfin calcule le score COEC réel pour chacun d&#x27;entre eux.</p><h3>generate_judgment_statistics()</h3><pre><code>def generate_judgment_statistics(df: pd.DataFrame) -> Dict:
    """Generate statistics about the judgment list."""
    stats = {
        'total_judgments': len(df),
        'unique_queries': df['qid'].nunique(),
        'unique_documents': df['docid'].nunique(),
        'grade_distribution': df['grade'].value_counts().to_dict(),
        'avg_judgments_per_query': len(df) / df['qid'].nunique() if df['qid'].nunique() > 0 else 0,
        'queries_with_clicks': len(df[df['grade'] > 1]['qid'].unique()),
        'click_through_rate': len(df[df['grade'] > 1]) / len(df) if len(df) > 0 else 0
    }
    return stats</code></pre><p>Il génère des statistiques utiles à partir de la liste de jugement, telles que le nombre total de requêtes, le nombre total de documents uniques ou la distribution des notes. Il s&#x27;agit d&#x27;une information purement informative qui ne modifie pas la liste de jugement qui en résulte.</p><h2>Résultats et impact</h2><p>Si vous suivez les instructions de la section Démarrage rapide, vous devriez obtenir un fichier CSV contenant une liste de jugements de 320 entrées (vous pouvez voir un <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/judgment_list.csv">exemple de sortie</a> dans le répertoire). Avec ces champs :</p><ul><li>qid : identifiant unique de la requête</li><li>docid : identifiant unique du document résultant</li><li>note : la note calculée pour la paire requête-document</li><li>requête : La requête de l&#x27;utilisateur</li></ul><p> Examinons les résultats de la requête &quot;recettes italiennes&quot; :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">qid</td><td style="border: 1px solid #ccc; padding: 4px;">docid</td><td style="border: 1px solid #ccc; padding: 4px;">grade</td><td style="border: 1px solid #ccc; padding: 4px;">Requête</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-italien-recettes</td><td style="border: 1px solid #ccc; padding: 4px;">recette_pasta_basics</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">Recettes italiennes</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-italien-recettes</td><td style="border: 1px solid #ccc; padding: 4px;">recette_pizza_margherita</td><td style="border: 1px solid #ccc; padding: 4px;">3.333333</td><td style="border: 1px solid #ccc; padding: 4px;">Recettes italiennes</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-italien-recettes</td><td style="border: 1px solid #ccc; padding: 4px;">guide_recette_risotto_guide</td><td style="border: 1px solid #ccc; padding: 4px;">10.0</td><td style="border: 1px solid #ccc; padding: 4px;">Recettes italiennes</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-italien-recettes</td><td style="border: 1px solid #ccc; padding: 4px;">recette_french_croissant</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">Recettes italiennes</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-italien-recettes</td><td style="border: 1px solid #ccc; padding: 4px;">recette_paella_espagnole</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">Recettes italiennes</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-italien-recettes</td><td style="border: 1px solid #ccc; padding: 4px;">recette_greek_moussaka</td><td style="border: 1px solid #ccc; padding: 4px;">1.875</td><td style="border: 1px solid #ccc; padding: 4px;">Recettes italiennes</td>
                    </tr>
              </tbody>
            </table>
          <p>Nous pouvons voir dans les résultats que pour la requête &quot;recettes italiennes&quot; :</p><ul><li>La recette de risotto est sans aucun doute le meilleur résultat pour la requête, recevant 10 fois plus de clics que prévu</li><li>La pizza Margherita est également un excellent résultat.</li><li>La mousaka grecque (surprenante) est également un bon résultat et se comporte mieux que ne le laisse supposer sa position dans les résultats. Cela signifie que quelques utilisateurs recherchant des recettes italiennes ont été intéressés par cette recette à la place. Ces utilisateurs sont peut-être intéressés par les plats méditerranéens en général. En fin de compte, cela nous indique qu&#x27;il pourrait s&#x27;agir d&#x27;un bon résultat à présenter dans le cadre des deux autres &quot;meilleurs&quot; matches dont nous avons parlé plus haut.</li></ul><h2>Conclusion</h2><p>L&#x27;utilisation des données UBI nous permet d&#x27;automatiser l&#x27;apprentissage des modèles LTR, en créant des listes de jugement de haute qualité à partir de nos propres utilisateurs. En utilisant l&#x27;algorithme COEC pour générer les notes, nous tenons compte des biais inhérents tout en reflétant ce qu&#x27;un utilisateur considère comme un meilleur résultat. La méthode décrite ici peut être appliquée à des cas d&#x27;utilisation réels afin d&#x27;offrir une meilleure expérience de recherche qui évolue en fonction des tendances d&#x27;utilisation réelles.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Évaluation des agents d'IA : Comment Elastic teste les frameworks agentiques]]></title>
            <description><![CDATA[Découvrez comment nous évaluons et testons les changements apportés à un système agentique avant de les communiquer aux utilisateurs d'Elastic afin de garantir des résultats précis et vérifiables.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/ai-agent-evaluation-elastic</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/ai-agent-evaluation-elastic</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Susan Chang, Abhimanyu Anand]]></dc:creator>
            <pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e681aab6ee77dfef38dc95e36c8535dfe7bcc105-721x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Introduction</h2><p>Dans la pile Elastic, il existe de nombreuses applications agentiques alimentées par LLM, telles que le futur agent Elastic AI dans<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder"> Agent Builder</a> (actuellement en tech preview) et <a href="https://www.elastic.co/docs/solutions/security/ai/attack-discovery">Attack Discovery</a> <a href="https://www.elastic.co/blog/whats-new-elastic-security-9-0-0">(GA</a> dans 8.18 et 9.0+), et d&#x27;autres sont en cours de développement. Pendant le développement, et même après le déploiement, il est important de répondre à ces questions :</p><ul><li>Comment évaluer la qualité des réponses de ces applications d&#x27;IA ?</li><li>Si nous apportons un changement, comment pouvons-nous garantir qu&#x27;il s&#x27;agit réellement d&#x27;une amélioration et qu&#x27;il n&#x27;entraînera pas une dégradation de l&#x27;expérience de l&#x27;utilisateur ?</li><li>Comment pouvons-nous facilement tester ces résultats de manière reproductible ?</li></ul><p>Contrairement aux tests de logiciels traditionnels, l&#x27;évaluation des applications d&#x27;IA générative fait appel à des méthodes statistiques, à un examen qualitatif nuancé et à une compréhension approfondie des objectifs des utilisateurs.</p><p>Cet article détaille le processus employé par l&#x27;équipe de développeurs d&#x27;Elastic pour effectuer des évaluations, garantir la qualité des changements avant leur déploiement et contrôler les performances du système. Nous voulons nous assurer que chaque changement est étayé par des preuves, ce qui permet d&#x27;obtenir des résultats fiables et vérifiables. Une partie de ce processus est intégrée directement dans Kibana, ce qui reflète notre engagement en matière de transparence dans le cadre de notre éthique des logiciels libres. En partageant ouvertement une partie de nos données d&#x27;évaluation et de nos paramètres, nous cherchons à renforcer la confiance de la communauté et à fournir un cadre clair à tous ceux qui développent des agents d&#x27;intelligence artificielle ou utilisent nos produits.</p><h2>Exemples de produits</h2><p>Les méthodes utilisées dans ce document ont servi de base à l&#x27;itération et à l&#x27;amélioration de solutions telles que Attack Discovery et Elastic AI Agent. Une brève présentation des deux, respectivement :</p><h3>Découverte d&#x27;attaques par Elastic Security</h3><p>Attack Discovery utilise les LLM pour identifier et résumer les séquences d&#x27;attaques dans Elastic. À partir des alertes d&#x27;Elastic Security dans un délai donné (24 heures par défaut), le flux de travail agentique d&#x27;Attack Discovery déterminera automatiquement si une ou plusieurs attaques ont eu lieu, ainsi que des informations importantes telles que l&#x27;hôte ou les utilisateurs compromis, et les alertes qui ont contribué à la conclusion de l&#x27;attaque.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/20fabb47642dad7b588daaaa8c3a98de860ad01d-1251x758.png" style="width: 100%;" /><p></p><p>L&#x27;objectif est que la solution basée sur le LLM produise un résultat au moins aussi bon que celui d&#x27;un humain.</p><h3>Agent d&#x27;IA élastique</h3><p>L&#x27;<strong>Elastic Agent Builder</strong> est notre nouvelle plateforme pour la création d&#x27;agents d&#x27;intelligence artificielle sensibles au contexte qui tirent parti de toutes nos capacités de recherche. Il est livré avec l&#x27;<strong>agent Elastic AI</strong>, un agent général préconstruit conçu pour aider les utilisateurs à comprendre et à obtenir des réponses à partir de leurs données par le biais d&#x27;une interaction conversationnelle.</p><p>L&#x27;agent y parvient en identifiant automatiquement les informations pertinentes dans Elasticsearch ou dans les bases de connaissances connectées et en tirant parti d&#x27;une série d&#x27;outils prédéfinis pour interagir avec elles. Cela permet à l&#x27;agent Elastic AI de répondre à un large éventail de requêtes d&#x27;utilisateurs, allant de la simple Q&amp;A sur un seul document à des demandes complexes nécessitant une agrégation et des recherches en une ou plusieurs étapes dans plusieurs index.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d29dee100bb8a17bb623acd745773a5164a1df4f-1600x1014.png" style="width: 100%;" /><h2>Mesurer les améliorations au moyen d&#x27;expériences</h2><p>Dans le contexte des agents d&#x27;intelligence artificielle, une expérience est une modification structurée et testable du système conçue pour améliorer les performances sur des aspects bien définis (par exemple, l&#x27;utilité, l&#x27;exactitude, la latence). L&#x27;objectif est de répondre de manière définitive à la question suivante &quot;Si nous fusionnons ce changement, pouvons-nous garantir qu&#x27;il s&#x27;agit d&#x27;une véritable amélioration et qu&#x27;il ne dégradera pas l&#x27;expérience de l&#x27;utilisateur ?&quot;</p><p>La plupart des expériences que nous menons comprennent généralement</p><ul><li><strong>Une hypothèse :</strong> Une affirmation spécifique et falsifiable. <em>Exemple :</em> &quot;L&#x27;accès à un outil de découverte d&#x27;attaques améliore l&#x27;exactitude des requêtes liées à la sécurité.</li><li><strong>Critères de réussite :</strong> Des seuils clairs qui définissent ce qu&#x27;on entend par &quot;succès&quot;. <em>Exemple :</em> &quot;+5% amélioration du score de justesse sur l&#x27;ensemble de données de sécurité, pas de dégradation ailleurs&quot;.</li><li><strong>Plan d&#x27;évaluation :</strong> Comment nous mesurons le succès (mesures, ensembles de données, méthode de comparaison)</li></ul><p>Une expérience réussie est un processus systématique de recherche. Chaque changement, qu&#x27;il s&#x27;agisse d&#x27;une modification mineure ou d&#x27;un changement architectural majeur, suit ces sept étapes afin de garantir que les résultats sont significatifs et exploitables :</p><ul><li>Étape 1 : Identifier le problème</li><li>Étape 2 : Définir les indicateurs</li><li>Étape 3 : Formuler une hypothèse claire</li><li>Étape 4 : Préparation de l&#x27;ensemble de données d&#x27;évaluation</li><li>Étape 5 : Exécuter l&#x27;expérience</li><li>Étape 6 : Analyse des résultats + itération</li><li>Étape 7 : Prendre une décision et la documenter</li></ul><p>Un exemple de ces étapes est illustré à la <em>figure 1.</em> Les sous-sections suivantes expliquent chaque étape, et nous développerons les détails techniques de chaque étape dans les documents à venir.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a9f5a3e92dd4922a795a19104c6e4ad8c98958d-2400x1352.png" style="width: 100%;" /><h2>Une démonstration pas à pas avec des exemples réels d&#x27;Elastic</h2><h3>Étape 1 : Identifier le problème</h3><p><em>Quel est exactement le problème que ce changement vise à résoudre ?</em></p><p>Exemple de découverte d&#x27;attaques : Les résumés sont parfois incomplets, ou une activité bénigne est signalée à tort comme une attaque (faux positifs).</p><p>Exemple d&#x27;agent d&#x27;IA élastique : La sélection des outils de l&#x27;agent, en particulier pour les requêtes analytiques, est sous-optimale et incohérente, conduisant souvent au choix du mauvais outil. Cela entraîne une augmentation des coûts des jetons et de la latence.</p><h3>Étape 2 : Définir les indicateurs</h3><p><em>Rendre le problème mesurable, afin de pouvoir comparer un changement à l&#x27;état actuel.</em></p><p>Les mesures courantes comprennent la <a href="https://developers.google.com/machine-learning/crash-course/classification/accuracy-precision-recall">précision et le rappel</a>, la <a href="https://en.wikipedia.org/wiki/Semantic_similarity">similarité sémantique</a>, la factualité, etc. Selon le cas d&#x27;utilisation, nous utilisons des vérifications de code pour calculer les mesures, telles que la correspondance des identifiants d&#x27;alerte ou des URL correctement récupérés, ou nous utilisons des techniques telles que LLM-as-judge pour des réponses plus libres.</p><p>Voici quelques exemples de mesures<em>(non exhaustifs</em>) utilisées dans les expériences :</p><p><strong>Attack Discovery</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrique</td><td style="border: 1px solid #ccc; padding: 4px;">Description</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Précision & rappel</td><td style="border: 1px solid #ccc; padding: 4px;">Faire correspondre les identifiants d'alerte entre les sorties réelles et les sorties prévues afin de mesurer la précision de la détection.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Similitude</td><td style="border: 1px solid #ccc; padding: 4px;">Utilisez BERTScore pour comparer la similarité sémantique du texte de la réponse.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Factualité</td><td style="border: 1px solid #ccc; padding: 4px;">Les principaux IOC (indicateurs de compromission) sont-ils présents ? Les tactiques de MITRE (taxonomie industrielle des attaques) sont-elles correctement prises en compte ?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Cohérence de la chaîne d'attaque</td><td style="border: 1px solid #ccc; padding: 4px;">Comparez le nombre de découvertes pour vérifier si l'attaque a été sur ou sous-déclarée.</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>Agent d&#x27;IA élastique</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrique</td><td style="border: 1px solid #ccc; padding: 4px;">Description</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Précision & rappel</td><td style="border: 1px solid #ccc; padding: 4px;">Faire correspondre les documents/informations récupérés par l'agent pour répondre à une requête de l'utilisateur avec les informations ou documents réels nécessaires pour répondre à la requête afin de mesurer la précision de la recherche d'informations.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Factualité</td><td style="border: 1px solid #ccc; padding: 4px;">Les faits essentiels nécessaires pour répondre à la demande de l'utilisateur sont-ils présents ? Les faits sont-ils dans le bon ordre pour les questions de procédure ?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Pertinence de la réponse</td><td style="border: 1px solid #ccc; padding: 4px;">La réponse contient-elle des informations périphériques ou sans rapport avec la requête de l'utilisateur ?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Complétude de la réponse</td><td style="border: 1px solid #ccc; padding: 4px;">La réponse répond-elle à toutes les parties de la requête de l'utilisateur ? La réponse contient-elle toutes les informations présentes dans la vérité terrain ?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Validation ES|QL</td><td style="border: 1px solid #ccc; padding: 4px;">La syntaxe de l'ES|QL générée est-elle correcte ? Est-il fonctionnellement identique à la vérité de terrain ES|QL ?</td>
                    </tr>
              </tbody>
            </table>
          <h3>Étape 3 : Formuler une hypothèse claire</h3><p><em>Établir des critères de réussite clairs en utilisant le problème et les paramètres définis ci-dessus.</em></p><p>Exemple d&#x27;agent d&#x27;IA élastique :</p><ol><li>Apporter des <strong>modifications aux descriptions des outils relevance_search et nl_search afin de définir clairement leurs fonctions spécifiques et leurs cas d&#x27;utilisation</strong>.</li><li>Nous prévoyons d&#x27;<strong>améliorer la</strong> <strong>précision de l&#x27;invocation des outils de</strong> <strong>25%</strong>.</li><li>Nous vérifierons qu&#x27;il s&#x27;agit d&#x27;un résultat positif net en nous assurant qu&#x27;il n&#x27;y a pas d&#x27;impact négatif sur d&#x27;autres indicateurs, par exemple l&#x27;<strong>exactitude et l&#x27;exhaustivité des données</strong>.</li><li>Nous pensons que cela fonctionnera parce que des <strong>descriptions précises des outils aideront l&#x27;agent à sélectionner et à appliquer avec plus de précision l&#x27;outil de recherche le plus approprié pour différents types de requêtes, ce qui réduira les erreurs d&#x27;application et améliorera l&#x27;efficacité globale de la recherche.</strong></li></ol><h3>Étape 4 : Préparation de l&#x27;ensemble de données d&#x27;évaluation</h3><p><em>Pour mesurer les performances du système, nous utilisons des ensembles de données qui représentent des scénarios réels.</em></p><p>Selon le type d&#x27;évaluation que nous menons, nous pouvons avoir besoin de différents types de formats de données, tels que les données brutes transmises à un LLM (par ex. scénarios d&#x27;attaque pour la découverte d&#x27;attaques) et les résultats attendus. Si l&#x27;application est un chatbot, les entrées peuvent être des requêtes d&#x27;utilisateurs, et les sorties peuvent être des réponses correctes du chatbot, des liens corrects qu&#x27;il aurait dû récupérer, etc.</p><p>Exemple de découverte d&#x27;attaque :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10 nouveaux scénarios d'attaque</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">8 épisodes de Oh My Malware (ohmymalware.com)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">4 scénarios multi-attaques (créés en combinant les attaques des 2 premières catégories)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">3 scénarios bénins</td>
                    </tr>
              </tbody>
            </table>
          <p>Exemple de jeu de données d&#x27;évaluation d&#x27;un agent d&#x27;IA élastique<a href="https://github.com/elastic/kibana/blob/main/x-pack/platform/packages/shared/onechat/kbn-evals-suite-onechat/evals/kb/kb.spec.ts">(lien vers le jeu de données Kibana</a>) :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">14 Indices utilisant des ensembles de données open source pour simuler des sources multiples dans KB.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">5 Types de requêtes (analytique, recherche de texte, hybride...)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">7 Types d'intentions d'interrogation (procédurale, factuelle - classification, enquête ; ...)</td>
                    </tr>
              </tbody>
            </table>
          <h3>Étape 5 : Exécuter l&#x27;expérience</h3><p>Exécuter l&#x27;expérience en générant des réponses à partir de l&#x27;agent existant et de la version modifiée par rapport à l&#x27;ensemble de données d&#x27;évaluation. Calculer des paramètres tels que la factualité (voir étape 2).</p><p>Nous mélangeons plusieurs évaluations basées sur les paramètres requis à l&#x27;étape 2 :</p><ul><li>Évaluation basée sur des règles (par exemple utiliser Python/TypeScript pour vérifier si .json est valide)</li><li>LLM-as-judge (demander à un LLM séparé si une réponse est cohérente avec un document source)</li><li>Examen humain dans la boucle pour les contrôles de qualité des nuances</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8648e75818d3291f0ac66f069438a500d42b8225-1600x1099.png" style="width: 100%;" /><h3>Étape 6 : Analyse des résultats + itération</h3><p>Maintenant que nous disposons des mesures, nous analysons les résultats. <em><span style="text-decoration:underline">Même si les résultats satisfont aux critères de réussite définis à l&#x27;étape 3, il faut encore procéder à un examen humain avant de fusionner la modification avec la production</span></em>; si les résultats ne satisfont pas aux critères, il faut procéder à une itération et résoudre les problèmes, puis effectuer les évaluations sur la nouvelle modification.</p><p>Nous pensons qu&#x27;il faudra quelques itérations pour trouver la meilleure modification avant de procéder à la fusion. De la même manière que l&#x27;on exécute des tests logiciels locaux avant de valider un projet, les évaluations hors ligne peuvent être exécutées avec des modifications locales ou plusieurs propositions de modifications. Il est utile d&#x27;automatiser l&#x27;enregistrement des résultats d&#x27;expériences, des scores composites et des visualisations pour rationaliser l&#x27;analyse.</p><h3>Étape 7 : Prendre une décision et la documenter</h3><p>Sur la base d&#x27;un cadre de décision et de critères d&#x27;acceptation, décider de fusionner les changements et documenter l&#x27;expérience. La prise de décision comporte de multiples facettes et peut prendre en compte des facteurs au-delà de l&#x27;ensemble de données d&#x27;évaluation, tels que la vérification des scénarios de régression sur d&#x27;autres ensembles de données ou l&#x27;évaluation du rapport coût-bénéfice d&#x27;un changement proposé.</p><p>Exemple : Après avoir testé et comparé quelques itérations, choisissez la modification la mieux notée et envoyez-la aux chefs de produit et aux autres parties prenantes concernées pour approbation. Joignez les résultats des étapes précédentes pour vous aider à prendre une décision. Pour plus d&#x27;exemples sur la découverte d&#x27;attaques, voir <a href="https://www.elastic.co/blog/elastic-security-generative-ai-features">Dans les coulisses des fonctions d&#x27;IA générative d&#x27;Elastic Security.</a></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/74c80b8f34dce8ddd20873ecb2f553873587ed35-1600x618.png" style="width: 100%;" /><h2>Conclusion</h2><p>Dans ce blog, nous avons parcouru le processus de bout en bout d&#x27;un flux d&#x27;expérimentation, illustrant comment nous évaluons et testons les changements apportés à un système agentique avant de les diffuser aux utilisateurs d&#x27;Elastic. Nous avons également fourni quelques exemples d&#x27;amélioration des flux de travail basés sur des agents dans Elastic. Dans les prochains billets de blog, nous développerons les détails des différentes étapes, telles que la création d&#x27;un bon ensemble de données, la conception de mesures fiables et la prise de décisions lorsque plusieurs mesures sont impliquées.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Connexion des agents élastiques à Gemini Enterprise via le protocole A2A]]></title>
            <description><![CDATA[Apprenez à utiliser Agent Builder pour exposer votre agent Elastic personnalisé à des services externes tels que Gemini Enterprise avec le protocole A2A.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jedr Blaszyk, Valerio Arvizzigno, Joe McElroy]]></dc:creator>
            <pubDate>Thu, 09 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5be8a425fab55dca2f9717d2e50812b0450fa625-1440x840.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Elastic Agent Builder</strong> est un ensemble de fonctionnalités permettant de créer des agents d&#x27;intelligence artificielle pilotés par les données directement dans Elasticsearch. Dans les articles précédents de cette <a href="https://www.elastic.co/search-labs/blog/series/context-aware-ai-agentic-workflows-with-elastic">série</a>, nous avons montré comment doter les agents personnalisés d&#x27;outils leur permettant d&#x27;effectuer des tâches complexes et leur fournir un ensemble d&#x27;instructions personnalisées pour guider leur comportement.</p><p>Mais qu&#x27;en est-il si vous souhaitez utiliser vos agents personnalisés avec les applications et les outils de productivité dont vous disposez déjà ?</p><p>C&#x27;est là qu&#x27;intervient le <strong>protocole Agent-to-Agent (A2A)</strong>. A2A est une <a href="https://github.com/a2aproject/A2A">norme ouverte</a> d&#x27;interopérabilité qui permet aux agents de différentes plateformes de communiquer et de collaborer. Et nous l&#x27;avons intégré directement dans l&#x27;Elastic Agent Builder.</p><p>Aujourd&#x27;hui, nous allons vous montrer comment prendre un agent personnalisé que vous avez construit et l&#x27;exposer à d&#x27;autres services, en particulier <strong>Gemini Enterprise </strong>(anciennement Agentspace).</p><h2>Le pouvoir des normes ouvertes : l&#x27;importance de l&#x27;A2A</h2><p>Dans l&#x27;article de blog <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">Votre premier agent Elastic</a>, nous avons montré comment créer des agents personnalisés, tels qu&#x27;un agent <em>Assistant financier</em> avec un accès sécurisé à vos données de marché. Mais sa valeur est limitée si vous ne pouvez pas rendre ses informations disponibles dans d&#x27;autres environnements, comme Gemini Enterprise, sans avoir à reconstruire votre travail.</p><p>C&#x27;est ce défi de l&#x27;interopérabilité qui freine l&#x27;IA agentique. Les agents ont besoin d&#x27;un langage commun pour communiquer entre les plateformes, ce qui est précisément le rôle du protocole A2A. Il fournit une couche de communication standard qui vous permet non seulement d&#x27;interagir directement avec votre agent, mais qui ouvre également la voie à un avenir où les agents spécialisés de votre organisation pourront collaborer et partager des informations.</p><p>Pour rendre cela possible, l&#x27;Elastic Agent Builder supporte nativement le protocole A2A à travers deux points de terminaison standard pour tous vos agents :</p><ol><li><strong>Le point de terminaison de la carte d&#x27;agent (<code>GET {your-kibana-url}/api/agent_builder/a2a/{agentId}.json</code></strong> )<strong>- </strong>Il s&#x27;agit de la carte de visite de votre agent personnalisé. Il fournit des métadonnées sur votre agent (nom, description, capacités, etc.) à tout service compatible A2A.</li><li><strong>Le point de terminaison du protocole A2A (<code>POST {your-kibana-url}/api/agent_builder/a2a/{agentId}</code>)</strong> - Il s&#x27;agit du canal de communication. D&#x27;autres agents envoient leurs demandes ici, et votre agent les traite et renvoie une réponse, conformément à la <a href="https://a2a-protocol.org/latest/specification/">spécification du protocole A2A</a>.</li></ol><h2>Testez votre agent avec l&#x27;inspecteur A2A</h2><p>Avant de connecter notre agent à un système de production, il est bon de vérifier qu&#x27;il communique correctement. Le moyen le plus simple d&#x27;y parvenir est d&#x27;utiliser l&#x27;<strong>inspecteur A2A</strong>, un outil spécialement conçu pour tester et déboguer les intégrations A2A.</p><p>La mise en route de l&#x27;inspecteur est simple. Vous pouvez cloner le dépôt <a href="https://github.com/a2aproject/a2a-inspector">a2a-inspector</a> et suivre les instructions du README pour <a href="https://github.com/a2aproject/a2a-inspector?tab=readme-ov-file#3-run-the-application">lancer l&#x27;application</a>. Une fois lancée, l&#x27;interface utilisateur est disponible par défaut à l&#x27;adresse <code>http://localhost:5001/</code>.</p><p>Pour connecter l&#x27;inspecteur A2A à votre agent, vous devez fournir deux informations essentielles :</p><ul><li>URL de la carte d&#x27;agent : Il s&#x27;agit du point de terminaison qui décrit votre agent. Pour l&#x27;<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">agent assistant financier de notre poste précédent</a>, cette URL serait <code>{your-kibana-url}/api/agent_builder/a2a/financial_assistant.json</code>.</li><li>En-tête d&#x27;authentification : Nous utiliserons une clé API standard pour l&#x27;authentification.</li></ul><p>Une fois que vous avez saisi ces informations dans l&#x27;interface utilisateur de l&#x27;inspecteur, vous pouvez vous connecter et commencer à dialoguer avec votre agent immédiatement.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7231c72bf30bed2a854f58658c1eca2843f43bfc-1600x1296.png" style="width: 100%;" /><p>Cette simple validation nous donne l&#x27;assurance que notre agent est configuré correctement et qu&#x27;il est prêt pour l&#x27;étape suivante.</p><h2>En direct ! Votre agent personnalisé dans Gemini Enterprise</h2><p>Passons maintenant à la partie la plus excitante : donner vie à notre agent financier personnalisé au sein de Gemini Enterprise (anciennement Agentspace). Cette intégration s&#x27;appuie sur l&#x27;<a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-ai-agent">agent Elastic AI, disponible sur Google Cloud Marketplace</a>.</p><p>Une fois connecté, Gemini Enterprise utilise le protocole A2A pour communiquer directement avec votre agent. C&#x27;est là que la véritable puissance de l&#x27;interopérabilité se manifeste : les utilisateurs peuvent désormais accéder aux informations approfondies et axées sur les données de votre agent Elasticsearch personnalisé sans jamais quitter leur environnement familier. Vous pouvez voir votre agent Elastic personnalisé dans la liste des agents :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/37a39e92ebf3d72c6c8014397cd8e846336173a4-1600x834.png" style="width: 100%;" /><p>Imaginez qu&#x27;un utilisateur de Gemini Enterprise demande :</p><p><em>&quot;Je m&#x27;inquiète du sentiment du marché. Pouvez-vous m&#x27;indiquer quels sont nos clients les plus exposés aux mauvaises nouvelles ?</em>&quot;</p><p>En coulisses, Gemini Enterprise achemine cette requête via le protocole A2A vers votre agent Elastic personnalisé. Votre agent utilise alors ses outils spécialisés pour interroger vos données, formuler une réponse et la renvoyer. Pour l&#x27;utilisateur final, l&#x27;expérience est transparente.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e5f126c1a27a51c6e69a767aa87c9f746b62e39c-1600x1044.png" style="width: 100%;" /><p>Et cela ne s&#x27;arrête pas là ! La réponse obtenue avec l&#x27;agent Elastic peut maintenant être utilisée comme contexte pour vos prochaines questions qui peuvent déclencher un agent spécialisé différent (par exemple, l&#x27;agent Elastic). l&#x27;agent de votre plateforme d&#x27;investissement pour ajuster l&#x27;exposition aux sociétés cotées). Le tout sans quitter votre barre de recherche.</p><p>Avec vos agents Elastic déployés sur Gemini Enterprise avec A2A, vous pouvez unifier l&#x27;accès, l&#x27;orchestration et les flux de travail en éliminant les frictions entre l&#x27;IA, la recherche et les systèmes d&#x27;entreprise en offrant une interface utilisateur unique où les utilisateurs parlent à leurs données et à leurs outils - le tout en contexte. Pour les utilisateurs, cela signifie moins de changements d&#x27;outils et des assistants d&#x27;IA plus intuitifs et plus compétents. Pour les organisations, cela signifie une gouvernance cohérente, une évolutivité et une interopérabilité intégrées.</p><h2>À vous de construire</h2><p>Vous disposez désormais des outils nécessaires pour que vos agents élastiques soient disponibles partout. En tirant parti du protocole ouvert A2A, vous pouvez étendre la portée de vos agents personnalisés et sensibles aux données.</p><p>Dans cet article, nous vous avons présenté les principales étapes :</p><ul><li>Exposer votre agent via la carte d&#x27;agent A2A et les points d&#x27;extrémité du protocole.</li><li>Tester la connexion avec l&#x27;inspecteur A2A.</li><li>Intégrer votre agent en direct dans un service externe tel que Gemini Enterprise de Google.</li></ul><p>Vos agents n&#x27;ont plus besoin d&#x27;être isolés. Nous sommes impatients de voir les systèmes puissants et interconnectés que vous créerez. Bonne construction !</p><p>Le moyen le plus simple de commencer est d&#x27;essayer gratuitement Elastic Cloud sur <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud?pli=1">Google Cloud Marketplace</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Expériences d'amélioration des outils d'IA agentique pour Elasticsearch]]></title>
            <description><![CDATA[Découvrez comment nous avons amélioré les flux de travail des agents d'IA pour Elasticsearch par le biais d'expériences itératives en combinant les extracteurs linéaires, la recherche hybride et semantic_text pour une optimisation RAG évolutive.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/ai-agent-builder-experiments-performance</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/ai-agent-builder-experiments-performance</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sean Story]]></dc:creator>
            <pubDate>Mon, 06 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42903c869e034674b30bb36013345aaa97f6608b-1184x864.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Comme tout le monde ces jours-ci, ici à Elastic, nous nous lançons à fond dans le Chat, les agents et le RAG. Dans le département Search, nous avons récemment travaillé sur un Agent Builder et un Tool Registry, dans le but de rendre trivial le &quot;chat&quot; avec vos données dans Elasticsearch.</p><p>Lisez le <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">blog Building AI Agentic Workflows with Elasticsearch</a> pour en savoir plus sur la &quot;vue d&#x27;ensemble&quot; de cet effort, ou <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">Your First Elastic Agent : From a Single Query to an AI-Powered Chat</a> pour une introduction plus pratique.</p><p>Dans ce blog, nous allons nous intéresser à l&#x27;une des premières choses qui se produisent lorsque vous commencez à discuter et vous présenter quelques-unes des améliorations récentes que nous avons apportées.</p><h2>Que se passe-t-il ici ?</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" style="width: 100%;" /><p>Lorsque vous discutez avec vos données Elasticsearch, notre agent IA par défaut suit ce flux standard :</p><ol><li>Inspecter l&#x27;invite.</li><li>Identifiez l&#x27;index susceptible de contenir les réponses à cette question.</li><li>Générer une requête pour cet index, sur la base de l&#x27;invite.</li><li>Effectuez une recherche dans cet index avec cette requête.</li><li>Synthétiser les résultats.</li><li>Les résultats peuvent-ils répondre à l&#x27;invitation ? Si oui, répondez. Si ce n&#x27;est pas le cas, répétez l&#x27;opération, mais essayez quelque chose de différent.</li></ol><p>Cela ne devrait pas sembler trop nouveau - il s&#x27;agit simplement de Retrieval Augmented Generation (RAG). Et comme on peut s&#x27;y attendre, la qualité de vos réponses dépend fortement de la pertinence de vos premiers résultats de recherche. En travaillant à l&#x27;amélioration de la qualité de nos réponses, nous avons donc accordé une attention toute particulière aux requêtes générées à l&#x27;étape 3 et exécutées à l&#x27;étape 4. Et nous avons remarqué une tendance intéressante.</p><p>Souvent, lorsque nos premières réponses étaient &quot;mauvaises&quot;, ce n&#x27;était pas parce que nous avions lancé une mauvaise requête. C&#x27;est parce que <em>nous avions choisi le mauvais index</em> à interroger. Les étapes 3 et 4 ne nous posaient généralement pas de problème - c&#x27;était l&#x27;étape 2.</p><h2>Que faisions-nous ?</h2><p>Notre mise en œuvre initiale était simple. Nous avions construit un outil (appelé index_explorer) qui faisait effectivement un <code>_cat/indices</code> pour lister tous les indices disponibles, puis demandait au LLM d&#x27;identifier lequel de ces indices correspondait le mieux au message/à la question/à l&#x27;invitation de l&#x27;utilisateur. Vous pouvez voir cette <a href="https://github.com/elastic/kibana/blob/0cc78184957fcd12110dabae50353392ea937508/x-pack/platform/packages/shared/onechat/onechat-genai-utils/tools/index_explorer.ts#L98-L113">mise en œuvre originale ici.</a></p><pre><code>You are an AI assistant for the Elasticsearch company.
based on a natural language query from the user, your task is to select up to ${limit} most relevant indices from a list of indices.

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

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

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


    at createInferenceProviderError (errors.ts:90:10)
    at convertUpstreamError (convert_upstream_error.ts:39:38)
    at handle_connector_response.ts:26:33
    at Observable.init [as _subscribe] (/Users/seanstory/Desktop/Dev/kibana/node_modules/rxjs/src/internal/observable/throwError.ts:123:68)...</code></pre><p>Les premiers auteurs de l&#x27;outil l&#x27;avaient prévu. Si le mappage d&#x27;un index est une mine d&#x27;or d&#x27;informations, c&#x27;est aussi un bloc de JSON assez verbeux. Et dans un scénario réaliste où vous comparez de nombreux indices (notre ensemble de données d&#x27;évaluation en définit 20), ces blobs JSON s&#x27;accumulent. Nous voulons donc donner au LLM plus de contexte pour sa décision que de simples noms d&#x27;index pour toutes les options, mais pas autant que les mappings complets de chacune d&#x27;entre elles.</p><h3>Hypothèse 2 : des correspondances &quot;aplaties&quot; (listes de champs) en guise de compromis</h3><p>Nous sommes partis de l&#x27;hypothèse que les créateurs d&#x27;index utiliseront des noms d&#x27;index sémantiquement significatifs. Et si nous étendions cette hypothèse aux noms des champs ? Notre expérience précédente a échoué parce que le mappage de JSON comprend BEAUCOUP de métadonnées et d&#x27;éléments parasites.</p><pre><code>     "description_text": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          },
          "copy_to": [
            "description_semantic"
          ]
        },</code></pre><p>Le bloc ci-dessus, par exemple, compte 236 caractères et définit un seul champ dans une correspondance Elasticsearch. Alors que la chaîne &quot;description_text&quot; ne comporte que 16 caractères. Le nombre de caractères a été multiplié par près de 15, sans amélioration sémantique significative de la description de ce que ce champ implique à propos des données disponibles. Que se passerait-il si nous récupérions les correspondances pour tous les indices, mais qu&#x27;avant de les envoyer au LLM, nous les &quot;aplatissions&quot; en une simple liste de noms de champs ?</p><p>Nous avons essayé.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/112c2f447c11f154b5082725cd49b51d0a3c8a65-1214x804.png" style="width: 100%;" /><p>C&#x27;est formidable ! Des améliorations dans tous les domaines. Mais pourrions-nous faire mieux ?</p><h3>Hypothèse 3 : Descriptions dans le mapping _meta</h3><p>Si le simple fait de nommer les champs sans contexte supplémentaire a provoqué un tel saut, on peut supposer que l&#x27;ajout d&#x27;un contexte substantiel serait encore plus efficace ! Il n&#x27;est pas nécessairement conventionnel que chaque index soit accompagné d&#x27;une description, mais il est possible d&#x27;ajouter des métadonnées de tout type au niveau de l&#x27;index à l&#x27;objet _meta de la cartographie. Nous avons repris les index générés et ajouté des descriptions pour chaque index de notre ensemble de données. Tant que les descriptions ne sont pas trop longues, elles devraient utiliser moins de tokens que la cartographie complète et fournir de bien meilleures indications sur les données incluses dans l&#x27;index. Notre expérience a validé cette hypothèse.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/32d2692ad4479d0e52d8ee723dcc5710a6ec90f3-1208x806.png" style="width: 100%;" /><p>Une amélioration modeste, et nous sommes maintenant &gt;90% précis dans tous les domaines.</p><h3>Hypothèse 4 : La somme est plus grande que les parties</h3><p>Les noms de champs ont permis d&#x27;améliorer nos résultats. Les descriptions ont permis d&#x27;accroître nos résultats. L&#x27;utilisation des descriptions ET <em>des </em>noms de champs devrait donc permettre d&#x27;obtenir de meilleurs résultats, n&#x27;est-ce pas ?</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/114cbb408ff16b136251d2265416bd5270380fe5-1208x794.png" style="width: 100%;" /><p>Les données ont répondu &quot;non&quot; (pas de changement par rapport à l&#x27;expérience précédente). La théorie principale était que, puisque les descriptions ont été générées à partir des champs/mappings de l&#x27;index, il n&#x27;y a pas assez d&#x27;informations différentes entre ces deux éléments de contexte pour ajouter quelque chose de &quot;nouveau&quot; lorsqu&#x27;on les combine. En outre, la charge utile que nous envoyons pour nos 20 indices de test devient assez importante. Le raisonnement que nous avons suivi jusqu&#x27;à présent n&#x27;est pas extensible. En fait, il y a de bonnes raisons de croire qu&#x27;aucune des expériences que nous avons menées jusqu&#x27;à présent ne fonctionnerait sur des clusters Elasticsearch où il y a des centaines ou des milliers d&#x27;indices à choisir. Toute approche qui augmente linéairement la taille du message envoyé au LLM à mesure que le nombre total d&#x27;indices augmente n&#x27;est probablement pas une stratégie généralisable.</p><p>Ce dont nous avons vraiment besoin, c&#x27;est d&#x27;une approche qui nous aide à réduire un grand nombre de candidats aux options les plus pertinentes...</p><p>Il s&#x27;agit d&#x27;un problème de recherche.</p><h3>Hypothèse 5 : Sélection par recherche sémantique</h3><p>Si le nom d&#x27;un index a une signification sémantique, il peut être stocké sous forme de vecteur et faire l&#x27;objet d&#x27;une recherche sémantique.</p><p>Si les noms des champs d&#x27;un index ont une signification sémantique, ils peuvent être stockés sous forme de vecteurs et faire l&#x27;objet d&#x27;une recherche sémantique.</p><p>Si un index possède une description ayant une signification sémantique, il peut lui aussi être stocké sous forme de vecteur et faire l&#x27;objet d&#x27;une recherche sémantique.</p><p>Aujourd&#x27;hui, les index Elasticsearch ne rendent aucune de ces informations consultables (peut-être devrions-nous le faire !), mais il était assez simple de<a href="https://github.com/elastic/connectors/pull/3638"> bricoler quelque chose</a> qui pouvait combler cette lacune. En utilisant le cadre de connecteur d&#x27;Elastic, j&#x27;ai construit un connecteur qui produirait un document pour chaque index dans un cluster. Les documents de sortie ressembleraient à quelque chose comme :</p><pre><code> doc = {
                "_id": index_name,
                "index_name": index_name,
			"meta_description”: description,
"field_descriptions" = field_descriptions,
                "mapping": json.dumps(mapping),  
                "source_cluster": self.es_client.configured_host,
            }</code></pre><p>J&#x27;ai envoyé ces documents vers un nouvel index où j&#x27;ai défini manuellement le mappage :</p><pre><code>{
   "mappings": {
       "properties": {
           "semantic_content": {
               "type": "semantic_text"
           },
           "index_name": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "mapping": {
               "type": "keyword",
               "copy_to": "semantic_content"
           },
           "source_cluster": {
               "type": "keyword"
           },
           "meta_description": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "field_descriptions": {
               "type": "text",
               "copy_to": "semantic_content"
           }
       }
   }
}</code></pre><p>Cela crée un champ unique semantic_content, dans lequel tous les autres champs ayant une signification sémantique sont regroupés et indexés. La recherche dans cet index devient triviale, avec simplement :</p><pre><code>GET indexed-indices/_search
{
 "query": {
   "semantic": {
     "field": "semantic_content",
     "query": "$query"
   }
 }
}</code></pre><p>L&#x27;outil <code>index_explorer</code> modifié est maintenant <em>beaucoup</em> plus rapide, car il n&#x27;a pas besoin de faire une demande à un LLM, mais peut demander un seul encastrement pour la requête donnée et effectuer une opération de recherche vectorielle efficace. En prenant le premier hit comme index sélectionné, nous avons obtenu les résultats suivants :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" style="width: 100%;" /><p>Cette approche est évolutive. Cette approche est efficace. Mais cette approche est à peine meilleure que notre ligne de base. Ce n&#x27;est pas surprenant, car l&#x27;approche de la recherche est incroyablement naïve. Il n&#x27;y a aucune nuance. Aucune reconnaissance du fait que le nom et la description d&#x27;un index devraient avoir plus de poids qu&#x27;un nom de champ arbitraire que l&#x27;index contient. Pas de possibilité de pondérer les correspondances lexicales exactes par rapport aux correspondances synonymes. Cependant, la construction d&#x27;une requête très nuancée nécessiterait de supposer BEAUCOUP de choses sur les données disponibles. Jusqu&#x27;à présent, nous avons déjà fait des hypothèses importantes sur la signification sémantique des noms d&#x27;index et de champs, mais nous devrions aller plus loin et commencer à supposer la signification <em>qu</em> &#x27;ils ont et la manière dont ils sont liés les uns aux autres. Sans cela, nous ne pouvons probablement pas identifier de manière fiable la meilleure correspondance comme premier résultat, mais nous pouvons plus probablement dire que la meilleure correspondance se trouve quelque part dans les N premiers résultats. Nous avons besoin de quelque chose qui puisse consommer des informations sémantiques dans le contexte dans lequel elles existent, en les comparant à celles d&#x27;une autre entité qui peut se représenter d&#x27;une manière sémantiquement distincte, et juger entre elles. Comme un LLM.</p><h3>Hypothèse 6 : Réduction du nombre de candidats</h3><p>Il y a eu bien d&#x27;autres expériences que je vais passer sous silence, mais la principale avancée a été d&#x27;abandonner le désir de choisir la meilleure correspondance uniquement à partir d&#x27;une recherche sémantique, et d&#x27;utiliser plutôt la recherche sémantique comme un filtre pour éliminer les indices non pertinents de la considération du LLM. Nous avons combiné la recherche linéaire, la recherche hybride avec RRF et <code>semantic_text</code> pour <a href="https://gist.github.com/seanstory/d704443120e20f6c844db10e30066860">notre recherche</a>, en limitant les résultats aux 5 premiers indices correspondants.</p><p>Ensuite, pour chaque correspondance, nous avons ajouté le nom de l&#x27;index, la description et les noms des champs à un message pour le LLM. Les résultats ont été fantastiques :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8fcabd78f591f90d6bc7c0e087d31317e4eef791-1206x804.png" style="width: 100%;" /><p>La plus grande précision de toutes les expériences réalisées à ce jour ! Et comme cette approche n&#x27;augmente pas la taille du message proportionnellement au nombre total d&#x27;indices, elle est beaucoup plus évolutive.</p><h2>Résultats</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/04d630797213dbb8bf567da41d1cdd5c7b4586c9-1600x521.png" style="width: 100%;" /><p>Le premier résultat clair est que notre base de référence <em>peut être</em> améliorée. Cela semble évident rétrospectivement, mais avant le début de l&#x27;expérimentation, des discussions sérieuses ont eu lieu sur la question de savoir si nous devions abandonner complètement notre outil <code>index_explorer</code> et nous fier à la configuration explicite de l&#x27;utilisateur pour limiter l&#x27;espace de recherche. Bien que cela reste une option viable et valable, cette recherche montre qu&#x27;il existe des voies prometteuses vers l&#x27;automatisation de la sélection de l&#x27;indice lorsque les données de l&#x27;utilisateur ne sont pas disponibles.</p><p>Le résultat suivant a été que le simple fait d&#x27;ajouter des caractères de description au problème a un rendement décroissant. Avant cette recherche, nous nous demandions si nous devions investir dans l&#x27;extension de la capacité d&#x27;Elasticsearch à stocker des <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-field-meta">métadonnées au niveau des champs</a>. Aujourd&#x27;hui, ces valeurs <code>meta</code> sont plafonnées à 50 caractères, et l&#x27;on a supposé qu&#x27;il faudrait augmenter cette valeur pour pouvoir obtenir une compréhension sémantique de nos champs. Ce n&#x27;est manifestement pas le cas, et le LLM semble s&#x27;en sortir assez bien avec des noms de domaines. Nous pourrons approfondir cette question ultérieurement, mais elle ne nous semble plus urgente.</p><p>Inversement, cela a clairement démontré l&#x27;importance d&#x27;avoir des métadonnées d&#x27;index &quot;consultables&quot;. Pour ces expériences, nous avons piraté un index des indices. Mais c&#x27;est quelque chose que nous pourrions étudier en l&#x27;intégrant directement dans Elasticsearch, en créant des API pour le gérer, ou au moins en établissant une convention à ce sujet. Nous allons évaluer nos options et en discuter en interne, alors restez à l&#x27;écoute.</p><p>Enfin, cet effort a confirmé l&#x27;intérêt de prendre le temps d&#x27;expérimenter et de prendre des décisions fondées sur des données. En fait, cela nous a aidés à réaffirmer que notre produit Agent Builder aura besoin de capacités d&#x27;évaluation robustes et intégrées au produit. Si nous devons créer un ensemble de tests uniquement pour un outil qui prélève des indices, nos clients auront absolument besoin de moyens pour évaluer qualitativement leurs outils personnalisés au fur et à mesure qu&#x27;ils procèdent à des ajustements itératifs.</p><p>Je suis impatient de voir ce que nous allons construire, et j&#x27;espère que vous l&#x27;êtes aussi !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment déployer Elasticsearch sur AWS Marketplace]]></title>
            <description><![CDATA[Découvrez comment configurer et exécuter Elasticsearch à l'aide d'Elastic Cloud Service sur AWS Marketplace grâce à ce guide étape par étape.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/aws-elasticsearch-service-set-up</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/aws-elasticsearch-service-set-up</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Eduard Martin]]></dc:creator>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a80776d2ef85db26f850d932339fac2d26b90278-1086x620.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans cet article, nous allons apprendre comment déployer Elasticsearch sur AWS en utilisant les offres d&#x27;AWS Marketplace.</p><p>Nous allons utiliser Elastic Cloud Service sur AWS, qui est le service Elasticsearch géré officiel qui simplifie le déploiement et l&#x27;orchestration de tous les composants d&#x27;Elastic Stack via l&#x27;infrastructure native d&#x27;AWS.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f15814051b53b50bec38f9a9f515a1e6dc08a56c-884x440.png" style="width: 100%;" /><p>Si vous souhaitez savoir comment installer et configurer Elasticsearch sur AWS EC2, consultez <a href="https://www.elastic.co/search-labs/blog/elasticsearch-on-aws-ec2-deployment-guide">ce blog</a>.<br/></p><h2>Qu&#x27;est-ce qu&#x27;AWS Marketplace ?</h2><p><a href="https://aws.amazon.com/marketplace"><strong>Elastic sur AWS Marketplace</strong></a> offre une expérience de recherche et d&#x27;analyse entièrement gérée où AWS s&#x27;occupe du provisionnement de l&#x27;infrastructure, de la sécurité et du scaling, tandis que les développeurs se concentrent sur la création d&#x27;applications de recherche. Cela permet aux équipes de déployer des clusters Elasticsearch de niveau entreprise en quelques minutes grâce à des intégrations AWS prédéfinies.</p><h2>Quand utiliser Elastic sur AWS Marketplace ?</h2><p>Elastic sur AWS Marketplace est parfaitement adapté aux organisations disposant d&#x27;une infrastructure AWS existante et souhaitant déployer Elasticsearch avec des services gérés, une sécurité intégrée et des intégrations AWS transparentes sans frais opérationnels supplémentaires.</p><h2>Comment configurer Elastic Cloud sur AWS Marketplace</h2><h3>Étape 1 : Accédez à AWS Marketplace</h3><p>1. Connectez-vous à <a href="https://console.aws.com/">AWS</a></p><ul><li>Dans la barre de recherche, recherchez AWS Marketplace</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fc9928f79482c2c01e33978c88d390a2bfa2a3bf-1600x340.png" style="width: 100%;" /><p>2. Dans le panneau de navigation de gauche, cliquez sur <strong>Discover products</strong> (Découvrir les produits), puis recherchez Elasticsearch</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ca4232271cb13ebfe33de406ecaec085033ec8a0-1454x760.png" style="width: 100%;" /><p>3. Cliquez sur <strong>Elastic Cloud (Elasticsearch Service)</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e59aa276e55532f2ac3461d0ca983af4d41ad7a6-1600x611.png" style="width: 100%;" /><h3>Étape 2 : abonnez-vous au service</h3><p>1. Sélectionnez <strong>purchase options</strong> (options d&#x27;achat) ou cliquez sur <strong>Try for free</strong> (Essayer gratuitement)</p><p>2. Examinez les <strong>détails des tarifs</strong>, les <strong>conditions générales</strong> et les <strong>détails d&#x27;achat</strong>.</p><p>3. Cliquez sur le bouton <strong>Subscribe </strong>(S&#x27;abonner)</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c30d12b6b1061e76771d518011e522285f939f1-1600x290.png" style="width: 100%;" /><p>4. Nous devons maintenant configurer le compte Elastic. Suivez les étapes sur AWS</p><p>a. Cliquez sur le bouton Activer l’intégration</p><p>b. Cliquez sur le bouton Sign in (Connexion) ou Create a vendor account (Créer un compte vendeur)</p><p>c. Cliquez sur le bouton Launch template (Lancer le modèle)</p><p>d. Cliquez sur le bouton Launch Software (Lancer le logiciel)</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/879d9f0f01406e1955e1b38a2f6f2192ef040344-852x722.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fc59be78cf776aa12867f40810598419576cbd39-1600x1143.png" style="width: 100%;" /><h3>Étape 3. Configurez votre nouveau compte dans Elastic</h3><p>1. Créez votre compte Elastic</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5efeaf0737062a55470b17b67651f220e12183f2-986x905.png" style="width: 100%;" /><p>2. Validez votre adresse e-mail</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e0cfaac278614e317ce278935040bfa5a58edd13-853x894.png" style="width: 100%;" /><p>3. Saisissez votre nom et les informations de votre entreprise</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5658fe66c3b1bcced73e822eae006846f0ddd9e-997x903.png" style="width: 100%;" /><p>4. Répondez à un court questionnaire Elastic</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a3f53c00dadfd22f7d739a920c87d5f387182833-892x805.png" style="width: 100%;" /><p>5. Sélectionnez la région dans laquelle vous souhaitez héberger Elastic Cloud. Par défaut, votre région AWS actuelle sera sélectionnée</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1dcdf3bf91c305821daaa25a60aa03be6454c1c-1207x1032.png" style="width: 100%;" /><p>6. Attendez qu&#x27;Elastic se déploie</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/50903e57ebea7cc47bdfabf4750b4ba2a7a91148-1370x1266.png" style="width: 100%;" /><p>7. Votre déploiement est connecté à votre abonnement AWS Marketplace</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3bb731e2d5de5053ccecb77e45dbdbcdaf294dba-1600x1288.png" style="width: 100%;" /><h2>Annuler votre abonnement</h2><p>Pour annuler votre abonnement</p><p>1. Accédez à la <a href="https://console.aws.com/">console AWS</a></p><p>Recherchez AWS Marketplace dans la barre de recherche. Cliquez sur <strong>AWS Marketplace</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aeed3d1e67b4cef91934de257a6fd6daa9737a12-1600x554.png" style="width: 100%;" /><p>2. Cliquez sur <strong>Elastic Cloud subscription</strong> (Abonnement Elastic Cloud)</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/04e6cc41850226df223dbe2d1b0e4b45265f6c39-1600x564.png" style="width: 100%;" /><p>3. Cliquez sur le bouton <strong>Actions</strong>, puis sur <strong>Cancel subscription</strong> (Annuler l&#x27;abonnement)</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e33d334ea6541c637a223de3ebd6209def75a6d3-1600x1039.png" style="width: 100%;" /><p>4. Confirmez l&#x27;annulation, cliquez sur <strong>Yes </strong>puis sur le bouton cancel<strong> subscription</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b73b3187168adc7aefdd46f95be33c1bce3da1e4-1103x698.png" style="width: 100%;" /><p>5. Un message de confirmation apparaîtra en haut de la page</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c5ba66a23d535e866a8b458e5aca82c5f0b93037-1600x639.png" style="width: 100%;" /><h2>Étapes suivantes</h2><p>Démarrez votre expérience Elastic Cloud avec un essai gratuit de 7 jours comprenant un déploiement unique et trois projets<a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k"> Elastic Cloud (Elasticsearch Service)</a>. Connectez-vous simplement à votre compte AWS et cliquez sur &quot;View Purchase Options&quot; (Voir les options d&#x27;achat) pour commencer immédiatement à utiliser la plateforme Search AI d&#x27;Elastic sur Elastic<a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k"> Cloud (Elasticsearch Service)</a>. L&#x27;essai offre un accès complet aux solutions de recherche, de sécurité et d&#x27;observabilité sans aucun surcoût lié à la gestion de l&#x27;infrastructure.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AutoOps : Un voyage pour simplifier la gestion autonome d'Elasticsearch]]></title>
            <description><![CDATA[Découverte d'AutoOps pour Elasticsearch autogéré (environnements hébergés sur site ou privés). Nous présenterons ses avantages, sa configuration et les informations qu'il fournit.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elastic-autoops-self-managed-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elastic-autoops-self-managed-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Ori Shafir]]></dc:creator>
            <pubDate>Wed, 01 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4538ca4c6cdde99d88f1cead7515716fac217b92-1600x883.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Présentation d&#x27;AutoOps pour Elasticsearch autogéré (environnements hébergés sur site ou privés), qui facilite la gestion d&#x27;Elasticsearch. Plutôt qu&#x27;une <em>présentation technique classique</em>, cet article de blog met en lumière sa valeur ajoutée, sa configuration et les informations qu&#x27;il fournit du point de vue d&#x27;un ingénieur DevOps, car la véritable valeur d&#x27;AutoOps se révèle dans le travail quotidien de gestion d&#x27;Elasticsearch à grande échelle.</p><h2>Chapitre 1 : contexte - La complexité de l&#x27;autogestion à grande échelle</h2><p><strong>L&#x27;exploitation d&#x27;une plateforme de données autogérée à grande échelle peut s&#x27;avérer complexe.</strong></p><p>D&#x27;un moment à l&#x27;autre, les requêtes sont rapides comme l&#x27;éclair. L&#x27;instant d&#x27;après, l&#x27;ingestion prend du retard et les coûts de stockage grimpent en flèche. C&#x27;est un peu comme diriger un zoo, sauf que les animaux peuvent vous biper à 3 heures du matin.</p><p>Mon environnement n&#x27;est pas différent : plusieurs clusters, une importante recherche inter-clusters (CCS) et des centaines d&#x27;utilisateurs répartis dans plusieurs départements.</p><p>Nous utilisons Stack Monitoring pour nos opérations quotidiennes. Il fournit des graphiques et des mesures, mais il faut encore beaucoup d&#x27;expertise et de temps pour relier les points. Diagnostiquer les goulets d&#x27;étranglement ou savoir quand ajuster les stratégies de partage est encore un processus manuel et sujet aux erreurs. Dans de nombreux cas, les problèmes passent inaperçus jusqu&#x27;à ce qu&#x27;ils provoquent une panne, une baisse des performances ou un pic de stockage inattendu.</p><h2>Chapitre 2 : Découvrir l&#x27;AutoOps</h2><p>Puis l&#x27;annonce est tombée : <strong><a href="https://www.elastic.co/platform/autoops">AutoOps</a> est désormais disponible pour les clusters autogérés - sur site ou dans des environnements hébergés privés.</strong></p><p>AutoOps aide depuis longtemps les utilisateurs d&#x27;Elastic Cloud à gérer plus efficacement leurs déploiements. Désormais, ces mêmes avantages sont disponibles gratuitement pour les clusters autogérés (ECK, ECE ou autonomes) exécutés sur site ou dans des environnements de cloud privé, quel que soit votre niveau de licence.</p><p>L&#x27;argumentaire d&#x27;AutoOps est tentant :</p><ul><li>Détection en temps réel des goulots d&#x27;étranglement au niveau de l&#x27;ingestion, des shards déséquilibrés, des requêtes lentes, etc.</li><li>Des recommandations concrètes adaptées à la configuration de votre cluster</li><li>Optimisation des ressources pour améliorer l&#x27;efficacité et réduire les dépenses inutiles</li><li>Configuration simple grâce à l&#x27;installation d&#x27;un agent léger - aucune infrastructure supplémentaire n&#x27;est nécessaire.</li></ul><p>Honnêtement, tout ce qui promettait &quot;pas d&#x27;infrastructure supplémentaire&quot; retenait toute mon attention.</p><h2>Chapitre 3 : installation en 5 minutes (oui, vraiment)</h2><p>J&#x27;ai bloqué mon après-midi, j&#x27;ai fait des réserves de café et je me suis préparé à une longue installation. À ma grande surprise, cela n&#x27;a pris que cinq minutes :</p><ol><li>Connexion à mon <a href="https://cloud.elastic.co/registration?onboarding_service_type=ccm-autoops">compte Elastic Cloud</a></li><li>Vous avez choisi comment exécuter les agents ? Elastic Cloud Kubernetes (ECK), Kubernetes, Docker ou Linux.</li><li>Entrée de l&#x27;URL du cluster</li><li>Il n&#x27;y a qu&#x27;une seule commande à exécuter, qui installe un agent Metricbeat léger</li></ol><p>C&#x27;est tout. Mon cluster était connecté.</p><p>Il n&#x27;y a pas de groupes de surveillance spécialisés à prévoir. Et surtout, AutoOps n&#x27;envoie que des <strong>mesures, ce</strong> qui signifie que les données de mon entreprise sont conservées dans mon environnement autogéré.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cb8e1ebc0f23a6db6b0e4300c9cdcd165623ca39-1184x734.png" style="width: 100%;" /><p><em>Étape 1 : S&#x27;inscrire à Elastic Cloud</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b208830db4d74feef700473ce07b052e0c72f48b-3446x1716.png" style="width: 100%;" /><p><em>Étape 2 : Choisir l&#x27;emplacement de l&#x27;agent</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/850d8c0d6b9e018408969aa05b67dbd865a26d30-1184x734.png" style="width: 100%;" /><p><em>Étape 3 : Saisissez votre point de terminaison Elasticsearch et la manière de vous authentifier</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0a472889a277caabb9ecd3fb9cd57d3b3eac0100-1184x734.png" style="width: 100%;" /><p><em>Étape 4 : Commande simple pour installer l&#x27;agent</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f9a7283469a321a9c472d655cea11d9d0ea7b1e-1600x883.png" style="width: 100%;" /><p><em>C&#x27;est tout : après quelques minutes, AutoOps commencera à afficher des informations.</em></p><p>Pour plus de détails, reportez-vous à la <a href="https://www.elastic.co/docs/deploy-manage/monitor/autoops/cc-connect-self-managed-to-autoops">documentation AutoOps onboarding</a> et à la <a href="https://www.elastic.co/docs/deploy-manage/monitor/autoops/cc-cloud-connect-autoops-faq">FAQ.</a></p><h2>Chapitre 4 : premiers enseignements, premières victoires</h2><p>En l&#x27;espace de quelques minutes, AutoOps a commencé à faire émerger des idées, à fournir une analyse des causes profondes et des étapes claires pour les résoudre.</p><p>Les faits marquants de la première semaine sont les suivants :</p><ul><li>Signale les indices qui ne sont rattachés à aucune politique ILM et qui sont devenus trop importants.</li><li>L&#x27;un des clusters comportait trois nœuds vides résultant d&#x27;un travail de maintenance antérieur.</li><li>Certains nœuds franchissaient des filigranes et quelques indices manquaient de répliques.</li><li>Un modèle mal configuré a été détecté</li><li>Identifie une recherche en cours depuis longtemps et suggère la commande d&#x27;annulation exacte.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f7d16355938ffcb00127deaf833d7a4d0acdb188-1600x894.png" style="width: 100%;" /><p><em>AutoOps a détecté que le cluster rejette l&#x27;indexation.</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9d6372bc489e9954e6f78d81047d7a25feef1703-1600x894.png" style="width: 100%;" /><p><em>AutoOps a détecté que certains index sont configurés sans réplique</em><br/>Avant AutoOps, nous aurions utilisé plus de matériel pour résoudre ces problèmes. Au lieu de cela, l&#x27;AutoOps a permis de remonter directement à la cause première, et les corrections ont été effectuées en quelques minutes.</p><p>Pour une fois, un système de surveillance ne se contentait pas de me montrer des graphiques, il me disait comment résoudre le problème. J&#x27;ai commencé à me demander si AutoOps pouvait aussi aider à diagnostiquer mon Wi-Fi domestique et me libérer enfin du rôle de service informatique pour ma famille...</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eac09f1d730955453cd79900002b100bc741ad97-1600x894.png" style="width: 100%;" /><p><em>AutoOps surveille la taille des shards et alerte lorsqu&#x27;il y a beaucoup de shards vides.</em></p><h2>Chapitre 5 : un soutien qui voit ce que je vois</h2><p>La première fois que j&#x27;ai ouvert un dossier d&#x27;assistance, j&#x27;ai réalisé un autre avantage : les ingénieurs d&#x27;Elastic Support pouvaient voir exactement les mêmes données et recommandations que moi.</p><p>Le soutien s&#x27;est transformé en collaboration. Au lieu d&#x27;échanger des tickets, j&#x27;ai eu l&#x27;impression de travailler avec un coéquipier qui connaît Elasticsearch sur le bout des doigts.</p><h2>Chapitre 6 : opérer à l&#x27;échelle</h2><p>Avant AutoOps, la mise à l&#x27;échelle d&#x27;Elasticsearch relevait à la fois de la science, de l&#x27;instinct et de la connaissance tribale.</p><p>Désormais, il est axé sur les données, avec une visibilité et des recommandations claires :</p><ul><li>Visibilité de l&#x27;utilisation des ressources pour éviter le surapprovisionnement</li><li>Allocation plus intelligente des disques et recommandations de hiérarchisation pour des performances équilibrées</li><li>Des informations sur le dimensionnement de l&#x27;index qui réduisent les coûts de stockage et de matériel inutiles</li><li>Analyse plus rapide des causes profondes sur plusieurs clusters</li></ul><h2>Chapitre 7 : le premier d&#x27;une longue série de services connectés au nuage</h2><p>AutoOps est plus qu&#x27;un outil autonome. <a href="https://www.elastic.co/docs/deploy-manage/cloud-connect"><strong>Cloud</strong></a> Connect permet aux clusters autogérés d&#x27;utiliser les services Elastic Cloud sans avoir à supporter les coûts opérationnels liés à l&#x27;installation et à la gestion de ces services dans leur propre environnement. Les fonctionnalités sont déployées automatiquement, de sorte que les équipes bénéficient des améliorations plus rapidement et avec moins de complexité au niveau de l&#x27;infrastructure.</p><p>A suivre : Elastic Inference Service (EIS).</p><h2>Réflexions finales</h2><p>La gestion de déploiements à grande échelle et autogérés ne doit pas être une tâche insurmontable.</p><p>Et si vous souhaitez des opérations encore plus simples, vous pouvez toujours déplacer certaines charges de travail vers <a href="https://cloud.elastic.co/registration?pg=global&amp;plcmt=nav&amp;cta=205352-primary">Elastic Cloud</a>, qu&#x27;elles soient hébergées ou sans serveur, pour la manière la plus simple d&#x27;exécuter Elasticsearch.</p><p>Si vous souhaitez continuer à fonctionner de manière autogérée, connectez n&#x27;importe quel cluster autogéré à <a href="https://cloud.elastic.co/registration?onboarding_service_type=ccm-autoops">AutoOps dans Elastic Cloud</a>, sans frais pour toutes les licences Elastic.</p><h2>TLDR</h2><p>L&#x27;exécution de grands clusters Elasticsearch autogérés est complexe et prend du temps. AutoOps apporte une détection des problèmes en temps réel, des recommandations exploitables et une visibilité partagée avec Elastic Support, sans qu&#x27;il soit nécessaire de gérer une infrastructure supplémentaire. L&#x27;installation ne prend que quelques minutes et les résultats apparaissent immédiatement.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Votre premier agent Elastic : D'une simple requête à un chat alimenté par l'IA]]></title>
            <description><![CDATA[Apprenez à utiliser le constructeur d'agents d'IA d'Elastic pour créer des agents d'IA spécialisés. Dans ce blog, nous allons créer un agent d'IA financier.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/ai-agent-builder-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/ai-agent-builder-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
            <pubDate>Thu, 25 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ca853555eaa213f10f1db8c0ab0a2bbacee97b88-1456x816.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Avec le nouvel <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">Agent Builder</a> d&#x27;Elastic, vous pouvez créer des agents d&#x27;IA spécialisés qui agissent en tant qu&#x27;experts pour vos domaines d&#x27;activité spécifiques. Cette fonction vous permet d&#x27;aller au-delà des simples tableaux de bord et des barres de recherche, en transformant vos données d&#x27;une ressource passive en un partenaire actif et conversationnel.</p><p>Imaginez un gestionnaire financier qui doit se mettre à niveau avant une réunion avec un client. Au lieu de chercher manuellement dans les fils d&#x27;actualité et de croiser les tableaux de bord des portefeuilles, ils peuvent désormais simplement poser une question directe à leur agent personnalisé. C&#x27;est l&#x27;avantage d&#x27;une approche &quot;chat-first&quot;. Le gestionnaire a un lien direct et conversationnel avec ses données, en posant des questions telles que : &quot;Quelles sont les dernières nouvelles sur ACME Corp et comment cela affecte-t-il les avoirs de mon client ?&quot; et obtenir une réponse synthétisée et experte en quelques secondes.</p><p>Si nous construisons aujourd&#x27;hui un expert financier, les applications sont aussi variées que vos données. Le même pouvoir peut créer un analyste en cybersécurité pour traquer les menaces, un ingénieur en fiabilité de site pour diagnostiquer une panne ou un responsable marketing pour optimiser une campagne. Quel que soit le domaine, la mission principale est la même : transformer vos données en un spécialiste avec lequel vous pouvez discuter.</p><h2>Étape 0 : Notre ensemble de données</h2><p>Notre jeu de données du jour est un jeu de données synthétique à dominante financière, composé de comptes, de positions d’actifs, d’actualités économiques et de rapports financiers. Bien qu’il soit artificiel, il reproduit une version simplifiée d’un véritable jeu de données financières.</p><p><code>financial_accounts</code>: Portefeuilles de clients avec profils de risque</p><p><code>financial_holdings</code>: Positions en actions/ETF/obligations avec historique des achats</p><p><code>financial_asset_details</code>: Détails sur l&#x27;action/ETF/obligation</p><p><code>financial_news</code>: Articles de marché générés par l&#x27;IA avec analyse des sentiments</p><p><code>financial_reports</code>: Résultats de l&#x27;entreprise et notes des analystes</p><p>Vous pouvez charger vous-même cet ensemble de données en suivant le cahier d&#x27;accompagnement situé <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb">ici.</a></p><h2>Étape 1 : La base - Votre logique d&#x27;entreprise en tant qu&#x27;ES|QL</h2><p>Toute compétence en matière d&#x27;IA commence par un solide morceau de logique. Pour notre agent Financial Manager, nous devons lui apprendre à répondre à une question courante : &quot;Je m&#x27;inquiète du sentiment du marché. Pouvez-vous me montrer lesquels de nos clients sont les plus exposés aux mauvaises nouvelles ?&quot; Cette question va au-delà d&#x27;une simple recherche. Cela nous oblige à établir une corrélation entre le sentiment du marché et les portefeuilles des clients.</p><p>Nous devons trouver les actifs mentionnés dans les articles négatifs, identifier chaque client détenant ces actifs, calculer la valeur de marché actuelle de leur exposition, puis classer les résultats afin d&#x27;établir un ordre de priorité pour les risques les plus élevés. Cette analyse complexe et multi-joints est le travail parfait pour notre outil avancé ES|QL.</p><p>Voici la requête complète que nous utiliserons. Il est impressionnant, mais les concepts sont simples.</p><pre><code>FROM financial_news, financial_reports METADATA _index
        | WHERE sentiment == "negative"
        | WHERE coalesce(published_date, report_date) >= NOW() - TO_TIMEDURATION(?time_duration)
        | RENAME primary_symbol AS symbol
        | LOOKUP JOIN financial_asset_details ON symbol
        | LOOKUP JOIN financial_holdings ON symbol
        | LOOKUP JOIN financial_accounts ON account_id
        | WHERE account_holder_name IS NOT NULL
        | EVAL position_current_value = quantity * current_price.price
        | RENAME title AS news_title
        | KEEP
            account_holder_name, symbol, asset_name, news_title,
            sentiment, position_current_value, quantity, current_price.price,
            published_date, report_date
        | SORT position_current_value DESC
        | LIMIT 50</code></pre><h2>En bref : jonctions et garde-corps</h2><p>Deux concepts importants sont en jeu dans cette requête et font de l&#x27;agent un bâtisseur.</p><h3>1. La jointure LOOKUP</h3><p>Depuis des années, l&#x27;une des fonctionnalités les plus demandées d&#x27;Elasticsearch est la possibilité de joindre des données provenant de différents index sur la base d&#x27;une clé commune. Avec ES|QL, c&#x27;est désormais possible avec <code>LOOKUP JOIN</code>.</p><p>Dans notre nouvelle requête, nous effectuons une chaîne de trois <code>LOOKUP JOIN</code>: d&#x27;abord en reliant les nouvelles négatives aux détails des actifs, ensuite en reliant ces actifs aux avoirs des clients, et enfin en les reliant aux informations sur le compte du client. Cela permet d&#x27;obtenir un résultat incroyablement riche à partir de quatre indices différents en une seule requête efficace. Cela signifie que nous pouvons combiner des ensembles de données disparates pour créer une réponse unique et perspicace sans avoir à dénormaliser toutes nos données en un index géant au préalable.</p><h3>2. Les paramètres comme garde-fous du LLM</h3><p>Vous remarquerez que la requête utilise <code>?time_duration</code>. Il ne s&#x27;agit pas seulement d&#x27;une variable, mais d&#x27;un garde-fou pour l&#x27;IA. Si les grands modèles de langage (LLM) sont excellents pour générer des requêtes, le fait de leur laisser le champ libre sur vos données peut conduire à des requêtes inefficaces, voire incorrectes.</p><p>En créant une requête paramétrée, nous obligeons le LLM à travailler dans le cadre de la logique commerciale testée, efficace et correcte qu&#x27;un expert humain a déjà définie. Il s&#x27;agit d&#x27;une méthode similaire à celle utilisée par les développeurs depuis des années pour exposer en toute sécurité les capacités de recherche aux applications. L&#x27;agent peut interpréter une demande de l&#x27;utilisateur comme &quot;cette semaine&quot; pour remplir le paramètre <code>time_duration</code>, mais il doit utiliser notre structure de requête pour obtenir la réponse. Cela nous permet d&#x27;obtenir un équilibre parfait entre flexibilité et contrôle.</p><p>En fin de compte, cette requête permet à un expert qui comprend les données d&#x27;encapsuler ses connaissances dans un outil. D&#x27;autres personnes - et des agents d&#x27;intelligence artificielle - peuvent alors utiliser cet outil pour obtenir des résultats corrélés en fournissant simplement un seul paramètre, sans avoir besoin de connaître la complexité sous-jacente.</p><h2>Étape 2 : Les compétences - Transformer une requête en un outil réutilisable</h2><p>Une requête ES|QL n&#x27;est que du texte jusqu&#x27;à ce que nous l&#x27;enregistrions en tant qu&#x27;<strong>outil</strong>. Dans l&#x27;Agent Builder, un outil est plus qu&#x27;une simple requête sauvegardée ; c&#x27;est une compétence &quot;&quot; qu&#x27;un agent IA peut comprendre et choisir d&#x27;utiliser. La magie réside dans la <strong>description en langage naturel</strong> que nous fournissons. Cette description est la passerelle qui relie la question de l&#x27;utilisateur à la logique d&#x27;interrogation sous-jacente. Enregistrons la requête que nous venons de construire.</p><h3>Le chemin de l&#x27;interface utilisateur</h3><p>La création d&#x27;un outil dans Kibana est un processus simple.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a29c53a73b99af5972273c51218ea9004a9b0abb-1600x812.png" style="width: 100%;" /><p>1. Naviguer vers <strong>Agents</strong></p><ul><li>Cliquez sur<strong> Outils </strong>ou <strong>Gérer les outils</strong> et cliquez sur le bouton <strong>Nouvel outil.</strong></li></ul><p>2. Remplissez le formulaire avec les informations suivantes :</p><ul><li><strong>ID de l&#x27;outil :</strong> <code>find_client_exposure_to_negative_news</code></li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i. Il s&#x27;agit de l&#x27;identifiant unique de l&#x27;outil</p><ul><li><strong>Description :</strong> &quot;Détermine l&#x27;exposition du portefeuille du client aux nouvelles négatives. Cet outil analyse les nouvelles et les rapports récents pour y déceler un sentiment négatif, identifie l&#x27;actif associé et trouve tous les clients qui détiennent cet actif. Il renvoie une liste triée en fonction de la valeur de marché actuelle du poste afin de mettre en évidence le risque potentiel le plus élevé.&quot;</li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i. C&#x27;est ce que le LLM lit pour décider si cet outil est le bon pour le poste.</p><ul><li><strong>Étiquettes</strong>: <code>retrieval</code> et <code>risk-analysis</code></li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Les étiquettes sont utilisées pour regrouper plusieurs outils</p><ul><li><strong>Configuration :</strong> Coller la requête ES|QL complète de l&#x27;étape 1</li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i. Voici la recherche que l&#x27;agent utilisera</p><p>3. Cliquez sur <strong>Inférer les paramètres de la requête</strong>. L&#x27;interface utilisateur trouvera automatiquement le site <code>?time_duration</code>, dont la liste figure ci-dessous. Ajoutez une description simple pour chacun d&#x27;entre eux afin d&#x27;aider l&#x27;agent (et les autres utilisateurs) à comprendre leur fonction.</p><ul><li><code>time_duration</code>: Le délai pour rechercher des nouvelles négatives. Le format est le suivant : &quot;X heures&quot; DEFAUT 8760 heures</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/deb422d97863f78dbe08bfa2e3c708d1f75166ff-1600x938.png" style="width: 100%;" /><p>4. Testez-le !</p><ul><li>Cliquez sur Save &amp; test.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/57e768b88327821e70bd616744822f98fa367362-732x136.png" style="width: 100%;" /><ul><li>Une nouvelle fenêtre s&#x27;ouvre, dans laquelle vous pouvez tester la requête pour vous assurer qu&#x27;elle fonctionne comme prévu.</li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i. Dans <code>time_duration</code>, entrez la plage souhaitée, ici nous utilisons &quot;8760 heures&quot;</p><ul><li>Cliquez sur &quot;Submit&quot; et si tout se passe bien, vous verrez une réponse JSON. Pour vous assurer qu&#x27;il fonctionne comme prévu, faites défiler la page vers le bas et regardez l&#x27;objet <code>values</code>. C&#x27;est là que les documents correspondants sont renvoyés.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7e0c5171a4f7ffdfc1830f1a05a9acb987870b75-1600x722.png" style="width: 100%;" /><p>5. Cliquez sur le &quot;X&quot; en haut à droite pour fermer la fenêtre de test. Votre nouvel outil apparaît alors dans la liste, prêt à être attribué à un agent.</p><h3>Le chemin de l&#x27;API</h3><p>Pour les développeurs qui préfèrent l&#x27;automatisation ou qui ont besoin de gérer des outils par programme, vous pouvez obtenir le même résultat avec un seul appel d&#x27;API. Il suffit d&#x27;envoyer une demande <code>POST</code> au point de terminaison <code>/api/agent_builder/tools</code> avec la définition de l&#x27;outil.</p><pre><code>POST kbn://api/agent_builder/tools
{
  "id": "find_client_exposure_to_negative_news",
  "type": "esql",
  "description": "Finds client portfolio exposure to negative news. This tool scans recent news and reports for negative sentiment, identifies the associated asset, and finds all clients holding that asset. It returns a list sorted by the current market value of the position to highlight the highest potential risk.",
  "configuration": {
    "query": """
        FROM financial_news, financial_reports METADATA _index
        | WHERE sentiment == "negative"
        | WHERE coalesce(published_date, report_date) >= NOW() - TO_TIMEDURATION(?time_duration)
        | RENAME primary_symbol AS symbol
        | LOOKUP JOIN financial_asset_details ON symbol
        | LOOKUP JOIN financial_holdings ON symbol
        | LOOKUP JOIN financial_accounts ON account_id
        | WHERE account_holder_name IS NOT NULL
        | EVAL position_current_value = quantity * current_price.price
        | RENAME title AS news_title
        | KEEP
            account_holder_name, symbol, asset_name, news_title,
            sentiment, position_current_value, quantity, current_price.price,
            published_date, report_date
        | SORT position_current_value DESC
        | LIMIT 50
      """,
    "params": {
      "time_duration": {
        "type": "keyword",
        "description": """The timeframe to search back for negative news. Format is "X hours" DEFAULT TO 8760 hours """
      }
    }
  },
  "tags": [
    "retrieval",
    "risk-analysis"
  ]
}</code></pre><h2>Étape 3 : Les cerveaux - Créer votre agent personnalisé</h2><p>Nous avons créé une compétence réutilisable (l&#x27;outil). Nous devons maintenant créer l&#x27;<strong>agent</strong>, la personne qui l&#x27;utilisera réellement. Un agent est la combinaison d&#x27;un MLD, d&#x27;un ensemble spécifique d&#x27;outils auxquels vous lui donnez accès et, surtout, d&#x27;un ensemble d&#x27;<strong>instructions personnalisées</strong> qui agissent comme sa constitution, définissant sa personnalité, ses règles et son objectif.</p><h3>L&#x27;art de la proposition</h3><p>L&#x27;élément le plus important pour créer un agent fiable et spécialisé est la rapidité. Un ensemble d&#x27;instructions bien conçues fait la différence entre un chatbot générique et un assistant professionnel ciblé. C&#x27;est là que vous fixez les garde-fous, définissez les résultats et donnez à l&#x27;agent sa mission.</p><p>Pour notre agent <code>Financial Manager</code>, nous utiliserons l&#x27;invite suivante.</p><pre><code>You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

**Your Core Mission:**
- Respond accurately and concisely to natural language queries from financial managers.
- Provide precise, objective, and actionable information derived solely from the Elasticsearch data at your disposal.
- Summarize key data points and trends based on user requests.

**Reasoning Framework:**
1.  **Understand:** Deconstruct the user's query to understand their core intent.
2.  **Plan:** Formulate a step-by-step plan to answer the question. If you are unsure about the data structure, use the available tools to explore the indices first.
3.  **Execute:** Use the available tools to execute your plan.
4.  **Synthesize:** Combine the information from all tool calls into a single, comprehensive, and easy-to-read answer.

**Key Directives and Constraints:**
- **If a user's request is ambiguous, ask clarifying questions before proceeding.**
- **DO NOT provide financial advice, recommendations, or predictions.** Your role is strictly informational and analytical.
- Stay strictly on topic with financial data queries.
- If you cannot answer a query, state that clearly and offer alternative ways you might help *within your data scope*.
- All numerical values should be formatted appropriately (e.g., currency, percentages).

**Output Format:**
- All responses must be formatted using **Markdown** for clarity.
- When presenting structured data, use Markdown tables, lists, or bolding.

**Start by greeting the financial manager and offering assistance.**</code></pre><p>Voyons pourquoi cette incitation est si efficace :</p><ul><li><strong>Elle définit une personnalité sophistiquée : </strong>La première ligne établit immédiatement que l&#x27;agent est un assistant spécialisé en intelligence des données ( &quot;),&quot;, ce qui donne un ton professionnel et compétent.</li><li><strong>Il fournit un cadre de raisonnement : </strong>En disant à l&#x27;agent de &quot;Comprendre, Planifier, Exécuter et Synthétiser,&quot; nous lui donnons une procédure opérationnelle standard. Cela améliore sa capacité à traiter des questions complexes et à plusieurs étapes.</li><li><strong>Il favorise le dialogue interactif : </strong>L&#x27;instruction de &quot;poser des questions de clarification&quot; rend l&#x27;agent plus robuste. Il minimisera les hypothèses erronées sur les demandes ambiguës, ce qui permettra d&#x27;obtenir des réponses plus précises.</li></ul><h3>Le chemin de l&#x27;interface utilisateur</h3><p>1. Naviguez vers <strong>Agents.</strong></p><ul><li>Cliquez sur<strong> Outils </strong>ou <strong>Gérer les outils</strong> et cliquez sur le bouton <strong>Nouvel outil.</strong></li></ul><p>2. Complétez les informations de base :</p><ul><li><strong>ID de l&#x27;agent :</strong> <code>financial_assistant</code>.</li><li><strong>Instructions : </strong>Copiez le message ci-dessus.</li><li><strong>Étiquettes</strong>: <code>Finance</code>.</li><li><strong>Nom d&#x27;affichage :</strong> <code>Financial Assistant</code>.</li><li><strong>Description de l&#x27;affichage : </strong><code>An assistant for analyzing and understanding your financial data</code>.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/18ea73f1cae620129c0afa0e7ba9e2a3390224a7-1600x1189.png" style="width: 100%;" /><p>3. De retour en haut, cliquez sur <strong>Outils.</strong></p><ul><li>Cochez la case à côté de notre outil <code>find_client_exposure_to_negative_news</code>.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0c1e4ecbbd51d0dd10c6e861dbe9a9ccddeb35f6-1600x149.png" style="width: 100%;" /><p>4. Cliquez sur <strong>Enregistrer</strong>.</p><h3>Le chemin de l&#x27;API</h3><p>Vous pouvez créer exactement le même agent à l&#x27;aide d&#x27;une requête <code>POST</code> vers le point de terminaison <code>/api/agent_builder/agents</code>. Le corps de la demande contient toutes les mêmes informations : l&#x27;identifiant, le nom, la description, l&#x27;ensemble des instructions et une liste des outils que l&#x27;agent est autorisé à utiliser.</p><pre><code>POST kbn://api/agent_builder/agents
    {
      "id": "financial_assistant",
      "name": "Financial Assistant",
      "description": "An assistant for analyzing and understanding your financial data",
      "labels": [
        "Finance"
      ],
      "avatar_color": "#16C5C0",
      "avatar_symbol": "💰",
      "configuration": {
        "instructions": """You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

**Your Core Mission:**
- Respond accurately and concisely to natural language queries from financial managers.
- Provide precise, objective, and actionable information derived solely from the Elasticsearch data at your disposal.
- Summarize key data points and trends based on user requests.

**Reasoning Framework:**
1.  **Understand:** Deconstruct the user's query to understand their core intent.
2.  **Plan:** Formulate a step-by-step plan to answer the question. If you are unsure about the data structure, use the available tools to explore the indices first.
3.  **Execute:** Use the available tools to execute your plan.
4.  **Synthesize:** Combine the information from all tool calls into a single, comprehensive, and easy-to-read answer.

**Key Directives and Constraints:**
- **If a user's request is ambiguous, ask clarifying questions before proceeding.**
- **DO NOT provide financial advice, recommendations, or predictions.** Your role is strictly informational and analytical.
- Stay strictly on topic with financial data queries.
- If you cannot answer a query, state that clearly and offer alternative ways you might help *within your data scope*.
- All numerical values should be formatted appropriately (e.g., currency, percentages).

**Output Format:**
- All responses must be formatted using **Markdown** for clarity.
- When presenting structured data, use Markdown tables, lists, or bolding.

**Start by greeting the financial manager and offering assistance.**
""",
        "tools": [
          {
            "tool_ids": [
              "platform.core.search",
              "platform.core.list_indices",
              "platform.core.get_index_mapping",
              "platform.core.get_document_by_id",
              "find_client_exposure_to_negative_news"
            ]
          }
        ]
      }
    }</code></pre><h2>Étape 4 : La récompense - Avoir une conversation</h2><p>Notre logique d&#x27;entreprise est encapsulée dans un outil et un cerveau &quot;&quot; est prêt à l&#x27;utiliser dans notre agent. Il est temps de voir tout cela se concrétiser. Nous pouvons maintenant commencer à discuter avec nos données à l&#x27;aide d&#x27;un agent spécialisé.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5414cb6b7c41365acb0356a8bfe1140751ffd8db-1600x1014.png" style="width: 100%;" /><h3>Le chemin de l&#x27;interface utilisateur</h3><ol><li>Naviguez jusqu&#x27;à <strong>Agents </strong>dans Kibana.</li><li>En utilisant le menu déroulant en bas à droite de la fenêtre de chat, passez de l&#x27;<strong>agent Elastic AI</strong> par défaut à l&#x27;agent <strong>Financial Assistant </strong>que nous venons de créer.</li><li>Posez une question qui permettra à l&#x27;agent d&#x27;utiliser notre outil spécialisé :<ol><li><em>Je m&#x27;inquiète du sentiment du marché. Pouvez-vous m&#x27;indiquer quels sont nos clients les plus exposés aux mauvaises nouvelles ?</em></li></ol></li></ol><p>Après quelques instants, l&#x27;agent vous renvoie une réponse parfaitement formatée et complète. En raison de la nature des LLM, votre réponse peut être formatée légèrement différemment, mais pour cette exécution, l&#x27;agent a renvoyé :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/17b4ed43d279f9e53ee9fe3d482d0b2ec359a083-1600x1088.png" style="width: 100%;" /><h3>Que s&#x27;est-il passé ? Le raisonnement de l&#x27;agent</h3><p>L&#x27;agent ne s&#x27;est pas contenté de &quot;savoir&quot; la réponse. Elle a exécuté un plan en plusieurs étapes centré sur la sélection du meilleur outil pour le travail. Voici un aperçu de son processus de réflexion :</p><ul><li><strong>L&#x27;intention a été identifiée :</strong> Il a fait correspondre des mots clés de votre question, comme &quot;risk&quot; et &quot;negative news,&quot; à la description de l&#x27;outil <code>find_client_exposure_to_negative_news</code>.</li><li><strong>Exécution d&#x27;un plan :</strong> Il a extrait le délai de votre demande et a lancé un <strong>appel unique à</strong> cet outil spécialisé.</li><li><strong>Délégation du travail :</strong> L&#x27;outil a ensuite effectué toutes les opérations lourdes : les jointures enchaînées, les calculs de valeur et le tri.</li><li><strong>Synthèse du résultat :</strong> Enfin, l&#x27;agent a formaté les données brutes de l&#x27;outil en un résumé clair et lisible par l&#x27;homme, en suivant les règles de l&#x27;invite.</li></ul><p>Et nous ne sommes pas obligés de deviner, si nous élargissons notre réflexion et voyons plus de détails.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6a4da9262d3f88c60bfd8f8bf9b67c3b84e961ba-1600x607.png" style="width: 100%;" /><h3>Le chemin de l&#x27;API</h3><p>Vous pouvez entamer cette même conversation par le biais d&#x27;un programme. Il suffit d&#x27;envoyer la question d&#x27;entrée au point de terminaison de l&#x27;API <code>converse</code>, en veillant à spécifier le <code>agent_id</code> de notre <code>financial_manager</code>.</p><pre><code>POST kbn://api/agent_builder/converse
{
  "input": "Show me our largest positions affected by negative news",
  "agent_id": "financial_assistant"
}</code></pre><h2>Pour les développeurs : Intégrer l&#x27;API</h2><p>Si l&#x27;interface Kibana offre une expérience fantastique et intuitive pour la création et la gestion de vos agents, tout ce que vous avez vu aujourd&#x27;hui peut également être réalisé de manière programmatique. L&#x27;Agent Builder est construit sur un ensemble d&#x27;API, vous permettant d&#x27;intégrer cette fonctionnalité directement dans vos propres applications, pipelines CI/CD ou scripts d&#x27;automatisation.</p><p>Les trois principaux points d&#x27;aboutissement avec lesquels vous travaillerez sont les suivants :</p><ul><li><strong><code>/api/agent_builder/tools</code></strong>: Le point final pour créer, lister et gérer les compétences réutilisables que vos agents peuvent utiliser.</li><li><strong><code>/api/agent_builder/agents</code></strong>: Le point final pour la définition de vos personas d&#x27;agents, y compris leurs instructions très importantes et l&#x27;affectation des outils.</li><li><strong><code>/api/agent_builder/converse</code></strong>: Le point final pour interagir avec vos agents, entamer des conversations et obtenir des réponses.</li></ul><p>Pour une démonstration complète et pratique de l&#x27;utilisation de ces API pour réaliser chaque étape de ce tutoriel, consultez le <strong>bloc-notes Jupyter</strong> qui l&#x27;accompagne, disponible <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb">ici</a> dans notre dépôt GitHub.</p><h2>Conclusion : À vous de construire</h2><p>Nous avons commencé par prendre une requête ES|QL et la transformer en une compétence réutilisable. Nous avons ensuite créé un agent d&#x27;intelligence artificielle spécialisé, en lui donnant une mission et des règles claires, et nous l&#x27;avons doté de cette compétence. Il en résulte un assistant sophistiqué capable de comprendre une question complexe et d&#x27;exécuter une analyse en plusieurs étapes pour fournir une réponse précise et fondée sur des données.</p><p>Ce flux de travail est au cœur du nouvel <strong>Agent Builder d&#x27;</strong> Elastic. Il est conçu pour être suffisamment simple pour que les utilisateurs non techniques puissent créer des agents via l&#x27;interface utilisateur, mais suffisamment nuancé pour que les développeurs puissent créer des applications personnalisées basées sur l&#x27;IA à partir de nos API. Plus important encore, il vous permet de connecter en toute sécurité des LLM à vos propres données, régies par la logique experte que vous définissez, et de discuter avec vos données.</p><h2>Prêt à utiliser des agents pour dialoguer avec vos données ?</h2><p>La meilleure façon de consolider ce que vous avez appris est de vous salir les mains. Essayez tout ce que nous avons discuté aujourd&#x27;hui dans notre <a href="https://www.elastic.co/training/elastic-ai-agents-mcp"><strong>atelier pratique interactif et gratuit</strong></a>. Vous suivrez l&#x27;ensemble de ce processus et bien d&#x27;autres choses encore dans un environnement de type &quot;bac à sable&quot;.</p><p>Dans un prochain blog, nous vous montrerons comment utiliser une application autonome qui interagit avec notre agent <code>Financial Assistant</code> et nous nous pencherons sur le <strong>protocole de contexte de modèle (MCP)</strong> qui rend tout cela possible. Dans un autre blog, nous parlerons de la prise en charge par Agent Builder du protocole Agent2Agent, ou A2A, en cours de développement.</p><p>Restez à l&#x27;écoute et bonne construction !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire des flux de travail d'IA agentique avec Elasticsearch]]></title>
            <description><![CDATA[Découvrez Agent Builder, une nouvelle couche d'IA dans Elasticsearch qui fournit un cadre pour construire des flux de travail d'IA agentique, en utilisant la recherche hybride pour fournir aux agents le contexte dont ils ont besoin pour raisonner et agir.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/ai-agentic-workflows-elastic-ai-agent-builder</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/ai-agentic-workflows-elastic-ai-agent-builder</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Anish Mathur, Dana Juratoni]]></dc:creator>
            <pubDate>Tue, 23 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5d04bbcfc3925f48f3487ea4c7e0dd2205316d0-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Chez Elastic, nous avons apporté du contexte aux LLM et aux interfaces conversationnelles avec des assistants IA, des RAG avancés et des améliorations de la base de données vectorielle. Récemment, avec l&#x27;essor des agents d&#x27;intelligence artificielle, nous avons constaté que le besoin d&#x27;un contexte pertinent augmentait, et nous avons appris que<strong> les agents d&#x27;intelligence artificielle</strong> à fort impact ont besoin d&#x27;une recherche de qualité. Nous avons donc créé de nouvelles capacités natives dans la pile Elastic, conçues pour aider à développer des agents d&#x27;IA qui exploitent vos données dans Elasticsearch. Nous aimerions vous faire part de nos progrès dans ce domaine et de la direction que nous envisageons pour l&#x27;avenir.</p><h2>Agent Builder : Une base pour la construction d&#x27;agents d&#x27;intelligence artificielle pilotés par les données</h2><p>La promesse d&#x27;un agent d&#x27;intelligence artificielle est simple : donnez-lui un objectif et il fera le travail. Mais pour les développeurs, la réalité est une série de défis complexes. Tout d&#x27;abord, la qualité d&#x27;un agent dépend de sa perception de l&#x27;environnement et des outils qui lui sont fournis pour atteindre les objectifs de l&#x27;utilisateur. Par ailleurs, fournir le bon contexte à partir d&#x27;une mer de données d&#x27;entreprise diverses constitue un défi de taille. Enfin, tout cela doit être orchestré par une boucle de raisonnement fiable capable de planifier, d&#x27;exécuter et d&#x27;apprendre.</p><p>Pour résoudre ce problème, les développeurs doivent construire une pile complexe et fragile à partir de zéro. L&#x27;architecture actuelle des agents vous oblige à assembler plusieurs éléments disparates : un LLM, une base de données vectorielle, un magasin de métadonnées, des systèmes distincts pour la journalisation et la traçabilité, et un moyen d&#x27;évaluer si tout cela fonctionne. Ce n&#x27;est pas seulement complexe, c&#x27;est aussi coûteux, source d&#x27;erreurs, et cela rend difficile l&#x27;élaboration des systèmes d&#x27;IA de haute qualité et dignes de confiance que vos utilisateurs exigent.</p><p>Nous voulons donc simplifier les choses. Pour ce faire, notre approche consiste à prendre les éléments essentiels d&#x27;un agent contextuel efficace et à les intégrer directement au cœur d&#x27;Elasticsearch grâce à un nouvel ensemble de fonctionnalités appelé <strong>Elastic AI Agent Builder</strong>. Cette nouvelle couche fournit un cadre avec tous les éléments essentiels pour créer des agents d&#x27;intelligence artificielle alimentés par Elasticsearch : un ensemble ouvert de primitives, des protocoles basés sur des normes et un accès sécurisé aux données - afin que vous puissiez construire des systèmes agentiques adaptés aux données et aux exigences du monde réel :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ee1e73dd3f485ce86294d39490c98ce2a3d9925-1238x1072.png" style="width: 100%;" /><p><strong>Offrir des expériences d&#x27;IA</strong>: c&#x27;est l&#x27;objectif ultime. Avec notre Search AI Platform et vos données comme base, vous pouvez créer n&#x27;importe quel type d&#x27;application d&#x27;IA générative : des interfaces de chat personnalisées aux intégrations avec des frameworks agentiques comme LangChain ou des applications d&#x27;entreprise comme Salesforce.</p><p><strong>Exploité par des agents &amp; Tools</strong>: au-dessus de la plateforme, nous exposons une couche d&#x27;abstractions propre et simple. Vous interagissez directement avec les agents et les outils, que vous pouvez personnaliser pour répondre à vos besoins spécifiques. Vous pouvez également accéder aux capacités de la plateforme par le biais d&#x27;API robustes et de normes ouvertes telles que MCP et A2A.</p><p><strong>La Search AI Platform</strong>: il s&#x27;agit du moteur de base dans lequel nous avons intégré les composants. La base de données vectorielle avancée, la logique de l&#x27;agent, la construction des requêtes, les caractéristiques de sécurité, le traçage pour l&#x27;évaluation, tout cela vit ici, géré et optimisé par Elastic.</p><p><strong>Libérer la puissance de vos données</strong>: la base de tout grand agent est constituée de données de qualité. Notre plateforme commence par la capacité d&#x27;ingérer ou de fédérer l&#x27;accès à toutes les données de votre entreprise.</p><h2>Création d&#x27;agents dans la plate-forme</h2><p>Agent Builder, intégré à la Search AI Platform, fournit un cadre complet pour le développement d&#x27;agents. Il repose sur cinq piliers clés, chacun d&#x27;entre eux étant conçu pour traiter un aspect essentiel de la construction et du déploiement de systèmes d&#x27;IA de niveau de production. Voyons comment les agents définissent l&#x27;objectif, les outils fournissent les capacités, les normes ouvertes garantissent l&#x27;interopérabilité, l&#x27;évaluation assure la transparence et la sécurité assure la confiance.</p><h3>Agents</h3><p>Les agents sont le plus haut niveau de construction de cette nouvelle couche d&#x27;Elasticsearch. Un agent définit l&#x27;objectif à atteindre, l&#x27;ensemble des outils disponibles pour l&#x27;exécution et les sources de données sur lesquelles il peut agir. Les agents ne se limitent pas aux interactions conversationnelles ; ils peuvent alimenter des flux de travail complets, l&#x27;automatisation des tâches ou des expériences face à l&#x27;utilisateur.</p><p>Lorsqu&#x27;une demande est adressée à un agent, elle suit un cycle structuré :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/627ad1744b629bbe27359325702f40d97e40d1f4-704x852.png" style="width: 100%;" /><ol><li>Interpréter votre contribution et votre objectif</li><li>Sélectionner l&#x27;outil et les arguments adéquats pour l&#x27;exécution</li><li>Raisonner sur la réponse de l&#x27;outil</li><li>Décider si l&#x27;on renvoie un résultat ou si l&#x27;on poursuit les invocations d&#x27;outils.</li></ol><p>Elastic se charge de l&#x27;orchestration, du contexte et de l&#x27;exécution de ce cycle. Les développeurs se concentrent sur la définition de <em>ce que</em> l&#x27;agent doit faire : objectifs, outils et données, tandis que le système gère la <em>manière dont</em> le raisonnement et les flux de travail sont exécutés.</p><p><em>L&#x27;agent par défaut</em></p><p>Notre premier agent construit sur cette plateforme est un agent conversationnel natif dans Kibana, vous donnant la possibilité d&#x27;interagir immédiatement avec vos données. Il offre une expérience prête à l&#x27;emploi tout en restant totalement extensible et permet de commencer à interagir avec vos données immédiatement, sans configuration supplémentaire.</p><p>Vous pouvez interagir avec cette expérience directement dans Kibana par le biais d&#x27;une nouvelle expérience utilisateur de chat ou par le biais de l&#x27;API.</p><p>L&#x27;interrogation de l&#x27;agent par défaut via l&#x27;API ne nécessite qu&#x27;un seul appel :</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}</code></pre><p>Comme les conversations ont un état, vous pouvez continuer à interagir avec un agent à l&#x27;aide d&#x27;un numéro d&#x27;identification de la conversation ou récupérer l&#x27;historique complet de la conversation :</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "What about the second top?",
    "conversation_id": "ec757c6c-c3ed-4a83-8e2c-756238f008bb"
}

## get the full conversation
GET kbn://api/agent_builder/conversations/ec757c6c-c3ed-4a83-8e2c-756238f008bb</code></pre><p><em>Agents des douanes</em></p><p>Les développeurs peuvent également créer leurs propres agents personnalisés grâce à des API simples. Les agents encapsulent les instructions, les outils et l&#x27;accès aux données, créant ainsi des moteurs de raisonnement sur mesure.</p><p>La création d&#x27;un agent personnalisé est aussi simple qu&#x27;un simple appel à l&#x27;API. L&#x27;exemple ci-dessous montre un exemple, le champ &quot;configuration&quot; contient tous les détails clés, tels que les instructions ou les outils disponibles :</p><pre><code>POST kbn://api/agent_builder/agents
{
  "id": "custom_agent",
  "name": "My Custom Agent",
  "description": "Description of the custom agent",
  "configuration": {
      "instructions": "You are a log expert specialising in ...",
      "tools": 
...
   }
}</code></pre><p>Une fois créé, l&#x27;agent peut être interrogé directement :</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "What news about DIA?",
    "agent_id": "custom_agent"
}</code></pre><p>Cette approche transforme l&#x27;agent d&#x27;un système complexe à construire de toutes pièces en une unité simple et déclarative de logique d&#x27;entreprise, ce qui vous permet de mettre en place plus rapidement une automatisation intelligente.</p><p>Pour savoir comment créer un agent spécialisé à partir de zéro, consultez notre guide détaillé, étape par étape : <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">Votre premier agent Elastic : D&#x27;une simple requête à un chat alimenté par l&#x27;IA</a>.</p><h3>Outils</h3><p>Si les agents définissent <em>ce qu&#x27;il</em> faut accomplir, les outils définissent <em>comment.</em></p><p>Les outils exposent les capacités spécifiques du noyau Elastic pour que les agents puissent exécuter et récupérer des informations ou effectuer une action. Les outils peuvent inclure des fonctionnalités de base telles que l&#x27;obtention d&#x27;index ou de mappages, ou des fonctionnalités plus avancées telles que le langage naturel pour ES|QL.</p><p>Elasticsearch est livré avec un ensemble d&#x27;outils par défaut optimisés pour les besoins courants. Mais la véritable flexibilité réside dans la création de votre propre système. En définissant les outils, vous décidez exactement quelles requêtes, quels index et quels champs sont exposés à un agent avec ES|QL, ce qui vous permet de contrôler précisément la vitesse, la précision et la sécurité.</p><p>L&#x27;enregistrement d&#x27;un nouvel outil est aussi simple qu&#x27;un simple appel à l&#x27;API. Vous pouvez créer un outil qui exploite notre <a href="https://www.elastic.co/search-labs/blog/esql-timeline-of-improvements">ES|QL (Elasticsearch Query Language)</a> pour trouver des informations sur un actif financier spécifique :</p><pre><code>POST kbn://api/agent_builder/tools
{
  "id": "news_on_asset",
  "type": "esql",
  "description": "Find news and reports about a particular asset where ...",
  "configuration": {
    "query": "FROM financial_news, financial_reports | where MATCH(company_symbol, ?symbol) OR MATCH(entities, ?symbol) | limit 5",
    "params": {
      "symbol": {
        "type": "keyword",
        "description": "The asset symbol"
      }
    }
  ...
  }
...
}</code></pre><p>Une fois enregistré, vous pouvez attribuer le nouvel outil à vos agents personnalisés, en leur donnant un ensemble de capacités à raisonner et à invoquer chaque fois que cela est nécessaire.</p><p>Nous fournissons une plateforme pour créer des outils personnalisés pour vos besoins spécifiques, par exemple avec ES|QL qui transforme l&#x27;agent polyvalent en un expert spécifique à un domaine, fondé sur vos données uniques et votre domaine d&#x27;activité.</p><h3>Normes ouvertes et interopérabilité</h3><p>Les agents et outils Elasticsearch sont exposés via des API standard ouvertes, ce qui facilite leur intégration en tant que blocs fondamentaux dans l&#x27;écosystème plus large des cadres agentiques. Notre approche est simple : pas de boîte noire. Nous voulons que vous puissiez prendre la force principale d&#x27;Elastic en matière de recherche et l&#x27;associer à des capacités complémentaires et à d&#x27;autres systèmes agentiques.</p><p>Pour rendre cela possible, nous exposons nos capacités par le biais d&#x27;API, de protocoles émergents et de normes ouvertes.</p><p><em>Protocole de contexte de modèle (MCP)</em></p><p>Le <a href="https://www.elastic.co/search-labs/blog/model-context-protocol-elasticsearch">protocole MCP (Model Context Protocol)</a> est en passe de devenir la norme ouverte pour la connexion des outils entre les systèmes. En prenant en charge MCP, Elasticsearch peut connecter l&#x27;IA conversationnelle à vos bases de données, indices et API externes. Avec un serveur MCP distant intégré à la pile Elastic, tout client compatible MCP peut accéder aux outils d&#x27;Elastic et les utiliser comme éléments de construction dans vos flux de travail agentiques plus vastes.</p><p>Il ne s&#x27;agit pas d&#x27;une voie à sens unique. Vous pourrez également importer des outils à partir de serveurs MCP externes et les rendre disponibles dans Elasticsearch. Bientôt, les serveurs MCP seront probablement disponibles pour presque tout et seront bien plus complets que tout ce que nous pourrions créer nous-mêmes. Elastic fournit des fonctions de recherche et d&#x27;extraction à grande échelle, et vous pouvez les combiner avec des capacités spécialisées d&#x27;autres plateformes pour créer des agents efficaces.</p><p><em>Agent à agent (A2A)</em></p><p>Nous travaillons également sur la prise en charge des services d&#x27;agent à agent (A2A). Alors que le MCP concerne la connexion des outils, l&#x27;A2A concerne la connexion des agents. Avec un serveur A2A, les agents Elastic que vous créez pourront dialoguer directement avec des agents d&#x27;autres systèmes : partage de contexte, délégation de tâches et coordination de flux de travail.</p><p>Il s&#x27;agit d&#x27;une interopérabilité au niveau du raisonnement. Votre agent Elastic pourrait se charger de la recherche et de l&#x27;extraction, puis confier une tâche à un agent spécialisé dans l&#x27;assistance ou les technologies de l&#x27;information, et recevoir le résultat en retour de manière transparente. Il en résulte un écosystème d&#x27;agents coopérants, chacun faisant ce qu&#x27;il fait le mieux.</p><p>En fin de compte, l&#x27;adoption de MCP et d&#x27;A2A renforce notre engagement en faveur du rôle d&#x27;Elasticsearch en tant que citoyen de première classe, garantissant une intégration ouverte dans l&#x27;ensemble de l&#x27;écosystème agentique.</p><h3>Recherche et évaluation</h3><p>Au fur et à mesure que la recherche s&#x27;intègre aux agents, le défi d&#x27;une évaluation efficace devient crucial. Pour déployer en toute confiance des agents dans des environnements d&#x27;entreprise réels, vous devez avoir l&#x27;assurance qu&#x27;ils sont non seulement précis, mais aussi efficaces et fiables. Comment mesurer les performances, diagnostiquer une mauvaise réponse ou améliorer la situation de départ ? Tout commence par la visibilité.</p><p>C&#x27;est pourquoi nous avons conçu nos API pour la transparence dès le départ. Prenons l&#x27;exemple d&#x27;une simple interaction avec un agent :</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}</code></pre><p>La réponse comprend non seulement la réponse finale, mais aussi la trace complète de l&#x27;exécution, détaillant les outils sélectionnés par l&#x27;agent, les paramètres utilisés et les résultats de chaque étape.</p><pre><code>{
  "conversation_id": "db5c0c8b-12bf-4928-a57e-d99129ad2fea",
  "steps": [
    {
      "type": "tool_call",
      "tool_call_id": "tooluse_Nfqr3mwtR92HTRIsTcGXZQ",
      "tool_id": ".index_explorer",
      "params": {
        "query": "indices containing portfolio data"
      },
      "results": [...]
    }
    // ... more steps ...
  ],
  "response": {
    "message": "Based on the information I've gathered...."
  }
}</code></pre><p>Une traçabilité et une journalisation complètes sont essentielles pour une boucle d&#x27;amélioration continue, et bientôt, vous pourrez stocker et visualiser ces traces d&#x27;agents directement dans Elasticsearch. Mieux encore, ces traces sont construites sur le protocole OpenTelemetry, ce qui garantit qu&#x27;elles sont normalisées et portables pour l&#x27;intégration avec la plateforme d&#x27;observabilité de votre choix.</p><p>Ce niveau de détail est la base d&#x27;une véritable boucle d&#x27;amélioration continue. Il vous permet d&#x27;élaborer une suite complète de tests, de déboguer les échecs, d&#x27;identifier les modes de défaillance afin d&#x27;éviter les régressions et de capturer les modèles de réussite afin d&#x27;affiner les performances. En fin de compte, cette approche fondée sur les données est la clé de la transformation d&#x27;un prototype prometteur en un système d&#x27;IA fiable de qualité industrielle.</p><h3>Security</h3><p>Au fur et à mesure que les agents et les outils deviennent plus performants, la sécurité n&#x27;est plus facultative, elle est fondamentale. L&#x27;exposition des API, l&#x27;automatisation des tâches et des flux de travail exigent que les systèmes d&#x27;entreprise soient fiables. D&#x27;autant plus que les agents commencent à automatiser de plus en plus de flux de travail. Il est donc essentiel de pouvoir sécuriser ces flux et de s&#x27;assurer qu&#x27;ils répondent aux exigences de l&#x27;entreprise.</p><p>Les capacités ci-dessus héritent toutes des contrôles déjà disponibles dans Elastic aujourd&#x27;hui, y compris le <a href="https://www.elastic.co/search-labs/blog/rag-and-rbac-integration">contrôle d&#x27;accès basé sur les rôles (RBAC)</a> pour les appels d&#x27;API et la gestion des clés d&#x27;API. Nous étendons également les mêmes contrôles à de nouveaux protocoles tels que le MCP. Cela signifie la prise en charge de normes telles que OAuth, ainsi que la possibilité d&#x27;intégrer des mécanismes d&#x27;authentification personnalisés.</p><p>Notre objectif est de vous donner la flexibilité nécessaire pour expérimenter des agents et des outils, tout en maintenant le niveau de sécurité, de conformité et de gouvernance exigé par votre organisation.</p><h2>Ce qui vient ensuite</h2><p>Nous ne nous contentons pas d&#x27;ajouter des fonctionnalités, nous développons Elasticsearch pour l&#x27;ingénierie contextuelle agentique. Nous prévoyons de poursuivre notre développement sur la base de ces principes :</p><p>1. Engagement en faveur des normes Open Source &amp;</p><p>Notre engagement en faveur de l&#x27;open source et des normes ouvertes garantit que ces capacités restent interopérables avec les cadres agentiques externes. Vous serez toujours en mesure de connecter, d&#x27;étendre et de composer des agents à travers votre écosystème tout en gardant le contrôle de vos données et de vos flux de travail.</p><p>2. Valeur du contexte</p><p>Le contexte d&#x27;un agent d&#x27;intelligence artificielle est son plus grand atout. La gestion du contexte lorsque les agents effectuent des recherches et des opérations de flux de travail peut être une tâche difficile. Nous nous appuyons sur les points forts d&#x27;Elastic pour résoudre les problèmes d&#x27;ingénierie contextuelle, en veillant à ce que les informations les plus pertinentes soient toujours disponibles pour votre agent.</p><p>3. Focus sur les flux de données agentiques</p><p>À l&#x27;avenir, les agents constitueront une source de données de plus en plus importante, y compris les résultats des agents (documents générés, rapports, visualisations) et les traces d&#x27;exécution des agents (leur raisonnement, les appels d&#x27;outils, la mémoire/le contexte). Elastic est bien adapté au traitement de ce type de données, et nous travaillons sur la recherche concernant l&#x27;analyse, l&#x27;évaluation et l&#x27;amélioration automatisée de ces données.</p><p>4. Sécurité et sûreté dès la conception</p><p>Les agents d&#x27;intelligence artificielle posent de nouveaux défis en matière de sécurité et de sûreté. Elastic a toujours été un leader en matière de solutions sécurisées, et nous continuons à intégrer des garde-fous de niveau entreprise, des contrôles d&#x27;accès et les principes &quot;zero-trust&quot;.</p><p>5. Intégré dans la plate-forme</p><p>Les capacités de création d&#x27;agents d&#x27;intelligence artificielle sont intégrées dans la plateforme Elasticsearch. Cela signifie que les capacités au niveau de la plateforme, telles que le traçage, l&#x27;évaluation, la visualisation et l&#x27;analyse, sont toutes applicables aux agents. Vous souhaitez développer des tableaux de bord basés sur les exécutions des agents - c&#x27;est intégré. Vous souhaitez évaluer les performances de l&#x27;agent d&#x27;IA à l&#x27;aide d&#x27;une analyse des sentiments - la plateforme le permet. Cela permet de construire un cycle de vie complet autour de vos expériences d&#x27;IA.</p><p>L&#x27;objectif d&#x27;Elastic est de vous donner les interfaces pour construire une IA conversationnelle et des flux de travail automatisés qui sont entièrement intégrés, extensibles et ancrés dans vos données. De plus amples détails techniques et des informations sur les progrès réalisés seront bientôt communiqués.</p><p>Agent Builder est disponible dès à présent en version privée. <a href="https://www.elastic.co/contact?pg=global&amp;plcmt=nav&amp;cta=205352">Connectez-vous avec nous</a> pour demander l&#x27;accès. Vous avez des questions ou des commentaires ? Connectez-vous avec notre communauté de développeurs dans notre <a href="https://elasticstack.slack.com/archives/C09GRHEQ4AG"><strong>espace de travail Slack</strong></a> ou sur notre <a href="https://discuss.elastic.co/c/search/84"><strong>forum de discussion.</strong></a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elastic Open Web Crawler en tant que code]]></title>
            <description><![CDATA[Apprenez à utiliser les actions GitHub pour gérer les configurations d'Elastic Open Crawler, de sorte que chaque fois que nous apportons des modifications au référentiel, celles-ci sont automatiquement appliquées à l'instance déployée du crawler.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elastic-open-crawler-config-as-code</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elastic-open-crawler-config-as-code</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
            <pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ac41f55c85ad3f647cb0e0d750ed80bacd397f3-1036x581.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Avec <a href="https://github.com/elastic/crawler">Elastic Open Web Crawler</a> et son architecture pilotée par CLI, il est désormais assez simple d&#x27;avoir des configurations de crawler versionnées et un pipeline CI/CD avec des tests locaux.</p><p>Traditionnellement, la gestion des robots d&#x27;indexation était un processus manuel et sujet aux erreurs. Il s&#x27;agissait de modifier les configurations directement dans l&#x27;interface utilisateur et de se débattre avec le clonage des configurations de crawl, le retour en arrière, la gestion des versions, etc. Traiter les configurations des robots comme du code résout ce problème en offrant les mêmes avantages que ceux que nous attendons du développement de logiciels : répétabilité, traçabilité et automatisation.</p><p>Ce flux de travail facilite l&#x27;intégration de l&#x27;Open Web Crawler dans votre pipeline CI/CD pour les retours en arrière, les sauvegardes et les migrations, tâches qui étaient beaucoup plus délicates avec les Elastic Crawlers précédents, tels que l&#x27;Elastic Web Crawler ou l&#x27;App Search Crawler.</p><p>Dans cet article, nous allons apprendre à.. :</p><ul><li>Gérer nos configurations de crawl en utilisant GitHub</li><li>Disposer d&#x27;une installation locale pour tester les pipelines avant de les déployer</li><li>Créer une configuration de production pour exécuter le robot d&#x27;exploration avec de nouveaux paramètres à chaque fois que nous apportons des modifications à notre branche principale.</li></ul><p>Vous pouvez trouver le dépôt du projet <em><strong><a href="https://github.com/llermaly/elastic-open-crawler-as-code">ici</a>. </strong>Pour l&#x27;instant, j&#x27;utilise Elasticsearch 9.1.3 et Open Web Crawler 0.4.2.</em></p><h2>Produits requis</h2><ul><li>Bureau Docker</li><li>Instance Elasticsearch</li><li>Machine virtuelle avec accès SSH (par exemple, AWS EC2) et Docker installé.</li></ul><h2>Étapes</h2><ol><li>Structure des dossiers</li><li>Configuration du robot</li><li>Fichier Docker-compose (environnement local)</li><li>Actions Github</li><li>Tests au niveau local</li><li>Déploiement vers prod</li><li>Modifications et redéploiement</li></ol><h2>Structure des dossiers</h2><p>Pour ce projet, nous aurons la structure de fichier suivante :</p><pre><code>├── docker-compose.yml # Local elasticsearch + crawler
├── config/crawler-config.yml # Crawler config
├── .github/workflows/deploy.yml # GH Action to deploy changes
├── local.sh # Script to run our local crawler</code></pre><h2>Configuration du robot</h2><p>Sous <code>crawler-config.yml,</code>, nous mettrons les éléments suivants :</p><pre><code>output_sink: elasticsearch
output_index: web-crawl-index
max_crawl_depth: 1

elasticsearch:
  host: ${ES_HOST}
  api_key: ${ES_API_KEY}
     
domains:
  - url: https://web-scraping.dev
    seed_urls:
      - https://web-scraping.dev/product/1
      - https://web-scraping.dev/product/2
      - https://web-scraping.dev/product/3</code></pre><p>Il s&#x27;agit d&#x27;une recherche à partir de <a href="https://web-scraping.dev/products">https://web-scraping.dev/products,</a> un site fictif pour les produits. Nous ne parcourrons que les trois premières pages du produit. Le paramètre <code>max_crawl_depth</code> empêchera le robot d&#x27;exploration de découvrir d&#x27;autres pages que celles définies comme <code>seed_urls</code> en n&#x27;ouvrant pas les liens qu&#x27;elles contiennent.</p><p>Elasticsearch <code>host</code> et <code>api_key</code> seront alimentés dynamiquement en fonction de l&#x27;environnement dans lequel nous exécutons le script.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f9831034e1c4ccb554d37bdd188f2824338355a0-890x624.png" style="width: 100%;" /><h2>Fichier Docker-compose (environnement local)</h2><p>Pour le site local <code>docker-compose.yml,</code>, nous allons déployer le crawler et un seul cluster Elasticsearch + Kibana, de sorte que nous puissions facilement visualiser nos résultats de crawling <strong><em>avant de</em></strong> les déployer en production.</p><pre><code>services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:9.1.3
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    ports:
      - "9200:9200"
    networks: [esnet]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9200"]
      interval: 5s
      timeout: 5s
      retries: 10

  kibana:
    image: docker.elastic.co/kibana/kibana:9.1.3
    environment:
      - ELASTICSEARCH_HOSTS=http://es01:9200
    ports:
      - "5601:5601"
    networks: [esnet]
    depends_on: [es01]

  crawler:
    image: docker.elastic.co/integrations/crawler:0.4.2
    environment:
      - ES_HOST=http://es01:9200
      - CRAWLER_JRUBY_OPTS=--server
    container_name: crawler
    volumes:
      - ./config:/home/app/config
    networks: [esnet]
    entrypoint: ["/home/app/bin/crawler", "crawl", "/home/app/config/crawl-config-final.yml"]
    stdin_open: true
    tty: true

networks:
  esnet:
    driver: bridge</code></pre><p>Notez que le crawler attend qu&#x27;Elasticsearch soit prêt à fonctionner.</p><h2>Actions Github</h2><p>Nous devons maintenant créer une action GitHub qui copiera les nouveaux paramètres et exécutera le crawler dans notre machine virtuelle à chaque poussée vers main. Ainsi, nous disposons toujours de la dernière configuration déployée, sans avoir à entrer manuellement dans la machine virtuelle pour mettre à jour les fichiers et exécuter le crawler. Nous allons utiliser AWS EC2 comme fournisseur de machines virtuelles.</p><p>La première étape consiste à ajouter l&#x27;hôte (<code>VM_HOST</code>), l&#x27;utilisateur de la machine (<code>VM_USER</code>), la clé RSA SSH (<code>VM_KEY</code>), l&#x27;hôte Elasticsearch (<code>ES_HOST</code>) et la clé API Elasticsearch (<code>ES_API_KEY</code>) aux secrets d&#x27;action GitHub :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e1075bc54151b4b94eac2a6bd2682e9997e6c709-1106x707.png" style="width: 100%;" /><p>De cette manière, l&#x27;action pourra accéder à notre serveur pour copier les nouveaux fichiers et exécuter le crawl.</p><p>Maintenant, créons notre fichier <code>.github/workflows/deploy.yml</code>:</p><pre><code>name: Deploy

on:
  push:
    branches: [main]

jobs:
  Deploy:
    name: Deploy to EC2
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - name: Deploy crawler
        env:
          HOSTNAME: ${{ secrets.VM_HOST }}
          USER_NAME: ${{ secrets.VM_USER }}
          PRIVATE_KEY: ${{ secrets.VM_KEY }}
          ES_HOST: ${{ secrets.ES_HOST }}
          ES_API_KEY: ${{ secrets.ES_API_KEY }}
        run: |
          # Save private key
          echo "$PRIVATE_KEY" > private_key
          chmod 600 private_key

          # Generate final config locally
          envsubst < config/crawler-config.yml > config/crawl-config-final.yml

          # Copy the config folder to VM
          scp -o StrictHostKeyChecking=no -i private_key -r config ${USER_NAME}@${HOSTNAME}:~/config

          # SSH into VM and run crawler
          ssh -o StrictHostKeyChecking=no -i private_key ${USER_NAME}@${HOSTNAME} << EOF
            docker run --rm \
              -v ~/config:/config \
              docker.elastic.co/integrations/crawler:latest jruby \
              bin/crawler crawl /config/crawl-config-final.yml
          EOF</code></pre><p>Cette action exécutera les étapes suivantes à chaque fois que des modifications seront apportées au fichier de configuration du crawler :</p><ol><li>Renseigner l&#x27;hôte Elasticsearch et la clé API dans la configuration yml</li><li>Copier le dossier config sur notre VM</li><li>Se connecter via SSH à notre VM</li><li>Exécuter le crawl avec la configuration que nous venons de copier depuis le repo</li></ol><h2>Tests au niveau local</h2><p>Pour tester notre crawler localement, nous avons créé un script bash qui remplit l&#x27;hôte Elasticsearch avec l&#x27;hôte local de Docker et démarre un crawl. Vous pouvez lancer <code>./local.sh</code> pour l&#x27;exécuter.</p><pre><code>#!/bin/bash

# Exit on any error
set -e

# Load environment variables
export ES_HOST="http://es01:9200"

# Generate final crawler config
envsubst < ./config/crawler-config.yml > ./config/crawl-config-final.yml

# Bring everything up
docker compose up --build</code></pre><p>Regardons Kibana DevTools pour confirmer que le site<code> web-crawler-index</code> a été correctement renseigné :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/18551635e8265866e389a9632c4e4540958e4468-990x723.png" style="width: 100%;" /><h2>Déploiement vers prod</h2><p>Nous sommes maintenant prêts à pousser vers la branche principale, ce qui déploiera le crawler dans votre machine virtuelle et commencera à envoyer des logs à votre instance Serverless Elasticsearch.</p><pre><code>git add .
git commit -m "First commit"
git push</code></pre><p>Cela déclenchera l&#x27;action GitHub, qui exécutera le script de déploiement dans la machine virtuelle et commencera l&#x27;exploration.</p><p>Vous pouvez confirmer que l&#x27;action a été exécutée en allant sur le dépôt GitHub et en visitant l&#x27;onglet &quot;Actions&quot; :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/67ba3a7164d7a8049fe5661264820826cb18ed64-667x325.png" style="width: 100%;" /><h2>Modifications et redéploiement</h2><p>Vous avez peut-être remarqué que le site <code>price</code> de chaque produit fait partie du champ &quot;body&quot; du document. L&#x27;idéal serait de stocker le prix dans un champ distinct afin de pouvoir utiliser des filtres.</p><p>Ajoutons cette modification au fichier <code>crawler.yml</code> afin d&#x27;utiliser des <a href="https://github.com/elastic/crawler/blob/main/docs/features/EXTRACTION_RULES.md">règles d&#x27;extraction</a> pour extraire le prix de la classe CSS <code>product-price</code>:</p><pre><code>output_sink: elasticsearch
output_index: web-crawl-index
max_crawl_depth: 1

elasticsearch:
  host: ${ES_HOST}
  api_key: ${ES_API_KEY}
     
  # Index ingest pipeline to process documents before indexing          
  pipeline_enabled: true
  pipeline: pricing-pipeline

domains:
  - url: https://web-scraping.dev
    seed_urls:
      - https://web-scraping.dev/product/1
      - https://web-scraping.dev/product/2
      - https://web-scraping.dev/product/3
    extraction_rulesets:
      - url_filters:
          - type: ends
            pattern: /product/*
        rules:
          - action: extract
            field_name: price
            selector: .product-price
            join_as: string
            source: html</code></pre><p>Nous constatons également que le prix comprend un signe de dollar (<code>$</code>), que nous devons supprimer si nous voulons exécuter des requêtes de plage. Nous pouvons utiliser un pipeline d&#x27;acquisition pour cela. Notez que nous y faisons référence dans notre nouveau fichier de configuration du crawler ci-dessus :</p><pre><code>PUT _ingest/pipeline/pricing-pipeline
{
  "processors": [
    {
      "script": {
        "source": """
                ctx['price'] = ctx['price'].replace("$","")
            """
      }
    }
  ]
}</code></pre><p>Nous pouvons exécuter cette commande dans notre cluster Elasticsearch de production. Pour celui de développement, comme il est éphémère, nous pouvons intégrer la création du pipeline dans le fichier <code>docker-compose.yml</code> en ajoutant le service suivant. Notez que nous avons également ajouté un <code>depends_on</code> au service crawler afin qu&#x27;il démarre après la création réussie du pipeline.</p><pre><code> crawler:
    image: docker.elastic.co/integrations/crawler:0.4.2
    environment:
      - ES_HOST=http://es01:9200
      - CRAWLER_JRUBY_OPTS=--server
    container_name: crawler
    volumes:
      - ./config:/home/app/config
    networks: [esnet]
    entrypoint: ["/home/app/bin/crawler", "crawl", "/home/app/config/crawl-config-final.yml"]
    depends_on:
      pipeline-init:
        condition: service_completed_successfully
    stdin_open: true
    tty: true  


  pipeline-init:
    image: curlimages/curl:latest
    depends_on:
      es01:
        condition: service_healthy
    networks: [esnet]
    entrypoint: >
        sh -c "
        echo 'Creating ingest pipeline...';
        curl -s -X PUT http://es01:9200/_ingest/pipeline/pricing-pipeline \\
          -H 'Content-Type: application/json' \\
          -d '{\"processors\":[{\"script\":{\"source\":\"ctx.price = ctx.price.replace(\\\"$\\\", \\\"\\\")\"}}]}';
        echo 'Pipeline created!';
        "</code></pre><p>Exécutons maintenant <code>`./local.sh`</code> pour voir les changements localement :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dc1801599344a9f69f072b07ff828c4ba3815d7b-738x473.png" style="width: 100%;" /><p>C&#x27;est très bien ! Poussons maintenant la modification :</p><pre><code>git add crawler-config.yml
git commit -m "added price CSS selector"
git push</code></pre><p>Pour confirmer que tout fonctionne, vous pouvez vérifier votre Kibana de production, qui devrait refléter les changements et afficher le prix comme un nouveau champ sans le signe du dollar.</p><h2>Conclusion</h2><p>Elastic Open Web Crawler vous permet de gérer votre crawler en tant que code, ce qui signifie que vous pouvez automatiser l&#x27;ensemble du pipeline - du développement au déploiement - et ajouter des environnements locaux éphémères et des tests sur les données explorées de manière programmatique, pour ne citer que quelques exemples.</p><p>Vous êtes invités à cloner le dépôt officiel et à commencer à indexer vos propres données à l&#x27;aide de ce flux de travail. Vous pouvez également lire <a href="https://www.elastic.co/search-labs/blog/semantic-search-open-crawler">cet article</a> pour apprendre comment effectuer une recherche sémantique sur les index produits par le crawler.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[L'expérience de l'éditeur ES|QL d'Elasticsearch par rapport à l'analyseur d'événements PPL d'OpenSearch]]></title>
            <description><![CDATA[Découvrez comment les fonctionnalités avancées d'ES|QL Editor accélèrent votre flux de travail, en contraste direct avec l'approche manuelle de PPL Event Analyzer d'OpenSearch. 
]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/opensearch-vs-elasticsearch-ppl-esql</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/opensearch-vs-elasticsearch-ppl-esql</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Libby Lin, George Kobar]]></dc:creator>
            <pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f1ff4ff4a00b3e5b084d4116cea6cabc82a2d816-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Le <a href="https://www.elastic.co/blog/getting-started-elasticsearch-query-language">langage de requête Elasticsearch</a> (ES|QL), généralement disponible depuis la version 8.14, présente un langage et un moteur de requête conçus pour la recherche, l&#x27;observabilité et les enquêtes de sécurité. Contrairement au langage de traitement par pipeline (PPL) d&#x27;OpenSearch, qui emprunte largement aux langages de traitement par pipeline existants, ES|QL a été conçu dès le départ pour se concentrer sur la finition, la convivialité et l&#x27;intégration transparente sur la plateforme Kibana.</p><p>Dans ce blog, nous allons explorer l&#x27;expérience du développeur de l&#x27;éditeur ES|QL dans Elasticsearch 9.1 en le comparant à PPL dans l&#x27;analyseur d&#x27;événements (PPL en abrégé) dans OpenSearch 3.2.</p><p>Les différences apparaissent rapidement : l&#x27;éditeur ES|QL offre une autocomplétion intelligente, une aide contextuelle, des requêtes recommandées et une prise en charge des requêtes entre clusters qui permettent aux utilisateurs débutants, mais aussi aux utilisateurs experts, d&#x27;être plus autonomes. La conception réfléchie de la rédaction ES|QL est également visible dans l&#x27;inspection intégrée des requêtes et l&#x27;intégration holistique par le biais des flux de travail Kibana, par exemple, avec les requêtes récentes.</p><p>En revanche, PPL n&#x27;offre pas de support comparable pour l&#x27;autocomplétion, l&#x27;orientation contextuelle et les requêtes distribuées, ce qui entraîne une courbe d&#x27;apprentissage plus raide et davantage d&#x27;essais et d&#x27;erreurs.</p><h2>Faciliter l&#x27;apprentissage et l&#x27;utilisation d&#x27;ES|QL</h2><p>Se lancer dans l&#x27;utilisation d&#x27;un nouveau langage d&#x27;interrogation peut souvent sembler insurmontable. L&#x27;éditeur ES|QL<strong>, </strong>intégré directement à <strong>Kibana Discover</strong>, est conçu pour faciliter ce processus en prenant en charge non seulement la création et le débogage des requêtes, mais aussi en accélérant la vitesse à laquelle vous vous familiarisez et vous vous sentez à l&#x27;aise avec le langage. Comme l&#x27;éditeur aide à réduire les frictions dans les tâches quotidiennes, vous pouvez vous concentrer sur la recherche de solutions plutôt que sur la syntaxe et les essais-erreurs. Pour en savoir plus sur ces principes et sur la manière dont nous les avons intégrés dans l&#x27;éditeur <a href="https://www.elastic.co/search-labs/blog/improving-esql-editor-experience-in-kibana">, cliquez ici.</a></p><p>Cette expérience d&#x27;éditeur n&#x27;est pas limitée à Discover ; il s&#x27;agit d&#x27;un module de code réutilisable que nous travaillons à <strong>intégrer dans d&#x27;autres parties de Kibana</strong>, telles que les tableaux de bord, les alertes Kibana et les cartes Kibana.</p><h3>Autocomplétion intelligente : accélérer la création de requêtes</h3><p>L&#x27;autocomplétion de l&#x27;éditeur ES|QL est complète, offrant des suggestions de fonctions compatibles, d&#x27;arguments, de littéraux et même de fonctions imbriquées, une capacité qui fait notablement défaut dans PPL. En fait, il a été reconstruit de fond en comble, comme indiqué <a href="https://www.elastic.co/search-labs/blog/esql-autocomplete-rebuilt">ici.</a></p><p>La validation s&#x27;exécute au fur et à mesure que l&#x27;utilisateur tape, comme indiqué <a href="https://www.elastic.co/search-labs/blog/improving-esql-editor-experience-in-kibana">ici</a>, et suggère des champs tout en notifiant l&#x27;utilisateur en cas d&#x27;erreur. Cela réduit la charge mentale des utilisateurs et permet d&#x27;éviter les erreurs dès le début du processus de création de la requête.</p><p>Exemple : Des champs et des fonctions compatibles sont proposés dans cette imbrication :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4d7b2819c34fab31bced7873257b8932b623fba-1502x473.png" style="width: 100%;" /><p>Ce que la PPL ne soutient pas :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f3a89b1255b8a3a762022a2704fdd1c2987e5f9-1013x335.png" style="width: 100%;" /><p>Même si l&#x27;autocomplétion intelligente vous guide à travers les fonctions compatibles, les arguments et les fonctions imbriquées, il se peut que vous souhaitiez mieux comprendre les options disponibles. C&#x27;est précisément là que l&#x27;aide contextuelle de l&#x27;éditeur ES|QL devient inestimable, offrant une assistance immédiate, au sein de l&#x27;éditeur, pour clarifier et améliorer le développement de votre requête.</p><h3>Une aide contextuelle au bout des doigts</h3><p>Il suffit d&#x27;un clic Ctrl-Espace pour obtenir des informations supplémentaires sur une commande générée par la saisie semi-automatique. Un panneau apparaît immédiatement avec des détails sur la fonction, l&#x27;argument ou le champ en question. Cette interaction légère permet aux développeurs de rester dans le flux, en leur fournissant des conseils juste à temps sans les obliger à quitter l&#x27;éditeur ou à rechercher de la documentation externe. Cela permet de réduire le temps perdu en recherches syntaxiques et d&#x27;éviter les erreurs courantes avant qu&#x27;elles ne se produisent.</p><p>Voici ce que cela donne en pratique :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2797f9b5e002dbd83c46475c4ed4dcdc86144a01-1343x522.gif" style="width: 100%;" /><p>PPL ne dispose pas de ce niveau d&#x27;orientation intégrée, ce qui oblige les utilisateurs à s&#x27;appuyer sur des documents externes ou à procéder par tâtonnements. Cette absence n&#x27;est pas seulement une caractéristique manquante ; elle met en évidence une disparité plus large dans la philosophie de conception. ES|QL donne la priorité à une expérience réfléchie et contextuelle qui s&#x27;adapte aux données et au flux de travail de l&#x27;utilisateur. Cette différence s&#x27;accentue au fur et à mesure que les requêtes deviennent plus complexes, ce qui fait de l&#x27;Éditeur ES|QL un environnement plus efficace et plus fiable, tant pour l&#x27;apprentissage que pour la production.</p><h3>Requêtes recommandées qui tiennent compte du contexte des données</h3><p>L&#x27;éditeur ES|QL propose des requêtes recommandées qui sont automatiquement adaptées aux données avec lesquelles vous travaillez, telles que les journaux. Au lieu de présenter un éditeur vierge, il fait apparaître les points de départ les plus pertinents pour les cas d&#x27;utilisation courants. La sélection d&#x27;une requête recommandée génère une requête canonique qui est immédiatement utilisable et peut être affinée si nécessaire. Cette approche accélère le développement des requêtes, en particulier pour les nouveaux utilisateurs qui ne connaissent pas encore toute la syntaxe.</p><p>Voici un exemple où un utilisateur sélectionne la requête &quot;Détecter le point de changement&quot; :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3284c9340d41298820fbf8c7702abad946b48248-925x370.gif" style="width: 100%;" /><p>Comparez cela à l&#x27;expérience PPL :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5c3e59dadc6210aede3366bdd081887bcbae7a54-969x798.png" style="width: 100%;" /><p>En revanche, PPL n&#x27;offre qu&#x27;une autocomplétion de base, ce qui vous laisse le soin d&#x27;élaborer des requêtes sans contexte ni structure. Ce manque d&#x27;orientation peut conduire à la frustration et au tâtonnement.<br/>Grâce aux requêtes recommandées de l&#x27;éditeur ES|QL qui tiennent compte des données, vous pouvez éviter de partir de zéro ou de mémoriser la syntaxe pour les tâches de routine. L&#x27;éditeur réduit la charge cognitive, aide à prévenir les erreurs et vous permet de vous concentrer sur la résolution de problèmes et sur des objectifs plus larges, tels que l&#x27;exécution de recherches entre clusters, plutôt que de vous débattre avec la construction d&#x27;une requête.</p><h2>Interrogation intuitive de l&#x27;ensemble des clusters</h2><p>L&#x27;autocomplétion de l&#x27;éditeur ES|QL reste supérieure, même lorsque l&#x27;on travaille avec plusieurs clusters distants <a href="https://elastic.aiops.work/search-labs/blog/esql-cross-cluster-search">avec CCS</a>. Voici pourquoi :</p><h3>L&#x27;éditeur ES|QL offre une autocomplétion transparente, même à travers les clusters</h3><p>L&#x27;autocomplétion dans l&#x27;éditeur ES|QL prend en charge non seulement les noms des clusters, mais aussi les<strong> index locaux et distants</strong>. Comme indiqué <a href="https://www.elastic.co/search-labs/blog/esql-cross-cluster-search">ici</a>, cela fonctionne grâce à l&#x27;architecture d&#x27;un nœud coordinateur, qui aide à valider et à générer le plan d&#x27;interrogation à envoyer aux nœuds locaux, à exécuter l&#x27;interrogation et à agréger les résultats avant de les renvoyer à l&#x27;utilisateur. Sans saisir le nom complet du cluster distant, la saisie de &quot; :&quot; lance le processus d&#x27;autocomplétion pour l&#x27;index distant. Et vous n&#x27;êtes pas limité au préfixe.</p><p>Il est ainsi facile de découvrir et d&#x27;interroger des ensembles de données distribués sans avoir à mémoriser des conventions de dénomination ou à changer de contexte.</p><p>Voici un exemple où l&#x27;utilisateur tape simplement &quot;clu:g&quot; pour localiser un index distant :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bae1fbacb2320e4d07f41291ea57c9bcf15bf8a5-1092x523.gif" style="width: 100%;" /><p>En revanche, la PPL ne fournit qu&#x27;une complétion de base pour les index locaux, avec des suggestions limitées aux correspondances de préfixes. Les clusters distants doivent être saisis manuellement, ce qui augmente la probabilité d&#x27;erreurs et ralentit la création de requêtes.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e38793c0cc7c6cc7dc0fd4779a3e24ffbb6e0838-1094x263.gif" style="width: 100%;" /><p>PPL ne fournit une complétion que pour les index locaux et les suggestions sont limitées au préfixe :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5310f824942f94485cace2558ea72c56a0971e22-862x197.png" style="width: 100%;" /><p>ES|QL va plus loin en <a href="https://www.elastic.co/docs/solutions/search/cross-cluster-search#exclude-problematic-clusters">autorisant les exclusions</a> directement à l&#x27;aide d&#x27;un signe négatif, ce qui vous permet de contrôler finement les grappes qui participent à votre exploration. Cette fonction est particulièrement utile lorsque vous travaillez avec des environnements hybrides, où vous pouvez vouloir inclure ou omettre des ensembles de données spécifiques lors d&#x27;investigations entre clusters.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f23ca17f58fbf8e6d27419c028274cb91f30a549-937x78.png" style="width: 100%;" /><p>Ces améliorations reflètent l&#x27;accent mis par Elasticsearch sur la réduction des frictions dans la recherche cross-cluster. En facilitant la construction et la gestion des requêtes distribuées, ES|QL Editor permet aux analystes et aux développeurs de se concentrer sur les idées plutôt que sur la syntaxe, alors que PPL laisse une plus grande part de ce fardeau à l&#x27;utilisateur. De même que l&#x27;éditeur ES|QL simplifie la création de requêtes inter-clusters, il fournit également des outils permettant d&#x27;inspecter l&#x27;exécution de ces requêtes, garantissant ainsi la transparence et le contrôle des performances sur plusieurs clusters.</p><h3>Utilisation de l&#x27;outil Inspect pour analyser les détails de la recherche transversale</h3><p>L&#x27;outil Inspect, accessible à partir de l&#x27;éditeur ES|QL, est conçu pour fournir des métadonnées contenant des informations explicites sur l&#x27;exécution de la requête dans tous les clusters. Cette fonctionnalité est activée dans Kibana Discover et est accessible directement dans l&#x27;inspecteur de requête, ce qui vous permet d&#x27;analyser la progression et les détails de la recherche, ce qui est particulièrement crucial pour la <strong>recherche cross-cluster</strong> <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-cross-clusters">(CCS).</a> Cette fonctionnalité vous permet de suivre l&#x27;évolution de la recherche et de comprendre comment les requêtes se déroulent dans des ensembles de données distribués.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5d1c204f70171526fff924c30ea8ad08121a0f8d-919x523.gif" style="width: 100%;" /><p>Cette visibilité détaillée de l&#x27;exécution des requêtes, en particulier pour les recherches distribuées complexes, vous permet de garantir des performances et un dépannage optimaux.</p><p>Au-delà de la compréhension des mécanismes des requêtes individuelles, ES|QL Editor améliore encore le parcours de l&#x27;utilisateur en intégrant des fonctionnalités essentielles dans l&#x27;ensemble de la plateforme Kibana, favorisant ainsi un flux de travail transparent et ininterrompu.</p><h2>Expérience des requêtes unifiées avec ES|QL et Kibana</h2><p>Le changement de contexte est l&#x27;une des sources de friction les plus courantes dans l&#x27;analyse pilotée par les requêtes. Vous devez souvent vous rappeler des requêtes que vous avez déjà écrites. Chaque interruption déconcentre et ralentit les investigations. L&#x27;éditeur ES|QL y remédie en intégrant l&#x27;historique des requêtes dans Kibana.</p><h3>Requêtes récentes</h3><p>La fonction <a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga">Requêtes récentes</a> de l&#x27;éditeur ES|QL vous aide à rester dans le flux en rendant les travaux antérieurs instantanément accessibles. Dans l&#x27;éditeur ES|QL de Discover, vous pouvez afficher, réexécuter et enregistrer vos 20 dernières requêtes, de sorte que les requêtes complexes ou fréquemment utilisées ne sont qu&#x27;à un clic de souris. Ces requêtes enregistrées se retrouvent également dans Kibana et s&#x27;intègrent aux tableaux de bord, aux visualisations, aux alertes et aux cartes, de sorte que vous n&#x27;avez pas besoin de quitter votre écran actuel ou de retaper des commandes à partir de zéro. Cela permet de réduire le travail répétitif, d&#x27;accélérer les enquêtes et de minimiser le risque d&#x27;erreurs.</p><p>Par exemple, un utilisateur peut utiliser les requêtes récentes dans l&#x27;éditeur ES|QL dans Discover (et les mettre en vedette) :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb0f9fe37b92dec421c394d31ae7d90afebe062e-1421x793.png" style="width: 100%;" /><p>Les requêtes récentes sont intégrées dans le tableau de bord :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b84cd3a9bdec58812360d2aba4fc7713363ee3cc-1411x797.png" style="width: 100%;" /><p>PPL n&#x27;offre pas de possibilité comparable, ce qui oblige les utilisateurs à recourir au copier-coller manuel ou à des notes externes pour réutiliser les requêtes. La différence est plus qu&#x27;une question de commodité ; elle reflète la stratégie d&#x27;Elastic qui consiste à faire d&#x27;ES|QL un langage véritablement intégré à l&#x27;écosystème Kibana. Avec des fonctionnalités telles que les requêtes récentes, l&#x27;éditeur ES|QL ne se contente pas de rationaliser les flux de travail quotidiens, il pose également les bases de fonctionnalités plus avancées, actuellement en phase de prévisualisation technique, ce qui garantit une évolution constante de l&#x27;expérience.</p><h2>Conclusion</h2><p>ES|QL est plus qu&#x27;une syntaxe ; elle reflète la stratégie d&#x27;Elastic visant à améliorer la façon dont les utilisateurs recherchent, explorent et analysent les données. Grâce à une autocomplétion intelligente, à des requêtes recommandées en fonction du contexte, à des conseils intégrés à l&#x27;éditeur et à des outils comme Inspect, l&#x27;éditeur ES|QL accélère l&#x27;apprentissage, réduit les erreurs et simplifie les flux de travail complexes tels que l&#x27;analyse croisée des clusters. Intégré à Kibana, il relie de manière transparente les requêtes aux tableaux de bord, aux alertes et aux visualisations pour un flux de travail ininterrompu.</p><p>En résumé, ES|QL n&#x27;est pas simplement un autre langage pipé ; il s&#x27;agit d&#x27;un moteur de requêtes bien pensé associé à une interface utilisateur intuitive qui redéfinit fondamentalement la façon dont vous interagissez avec vos données, en offrant une expérience intégrée, intelligente et en constante évolution qui contraste fortement avec la nature souvent séquentielle et moins guidée d&#x27;OpenSearch PPL.</p><h2>Prochaines étapes</h2><p>Ce blog ne fait qu&#x27;effleurer la surface d&#x27;ES|QL. Les prochains articles approfondiront les comparaisons avec OpenSearch PPL et exploreront les fonctionnalités géospatiales, de visualisation et les futures fonctionnalités de l&#x27;éditeur telles que les <a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls">contrôles</a> (déjà disponibles dans les tableaux de bord), les onglets d&#x27;exploration multi-données, la recherche en arrière-plan, l&#x27;historique des requêtes plus riche et FUSE.</p><h2>Essayez ES|QL dès aujourd&#x27;hui</h2><p>Vous pouvez tester ES|QL dans des projets <a href="https://www.elastic.co/cloud/serverless">Serverless</a> Elasticsearch entièrement gérés avec un <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/create-serverless-project">essai gratuit</a>. Il est également disponible dans les versions à partir de la 8.11, mais c&#x27;est dans les versions <a href="https://www.elastic.co/blog/whats-new-elastic-9-1-0">8.19 et 9.1</a> qu&#x27;il est le plus utile.</p><p>Démarrez en quelques minutes sur votre environnement local à l&#x27;aide d&#x27;une simple commande :</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Présentation du générateur de requêtes ES|QL pour le client Elasticsearch Ruby]]></title>
            <description><![CDATA[Apprenez à utiliser le constructeur de requêtes ES|QL récemment publié pour le client Elasticsearch Ruby. Un outil pour construire des requêtes ES|QL plus facilement avec du code Ruby.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/esql-query-builder-elasticsearch-ruby-client</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/esql-query-builder-elasticsearch-ruby-client</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Fernando Briano]]></dc:creator>
            <pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f8e1263ab53d356824a4fc539084151be80899db-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Nous avons récemment publié <a href="https://github.com/elastic/esql-ruby/"><code>elastic-esql</code></a>, un outil Ruby publié sous licence Apache 2. Cette gemme vous permet de construire les requêtes <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL</a> d&#x27;Elastic en Ruby idiomatique, que vous pouvez ensuite utiliser avec l&#x27;API de requête ES|QL. ES|QL permet aux développeurs de filtrer, de transformer et d&#x27;analyser les données stockées dans Elasticsearch au moyen de requêtes. Il utilise les tuyaux &quot;&quot; ( <code>|</code> ) pour travailler avec les données étape par étape. La gem utilise des fonctions Ruby à la place, que vous pouvez enchaîner à l&#x27;objet original pour construire des requêtes plus complexes :</p><p><strong>ESQL :</strong></p><pre><code>FROM sample_data | LIMIT 2 | SORT @timestamp DESC</code></pre><p><strong>Rubis :</strong></p><pre><code>Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending</code></pre><h2>Installation</h2><p>La gemme peut être installée à partir de RubyGems avec :</p><pre><code>gem install elastic-esql</code></pre><p>Il peut également être ajouté au fichier Gemfile d&#x27;un projet :</p><pre><code>gem 'elastic-esql'</code></pre><h2>Utilisation</h2><p>Vous pouvez soit construire une requête complète en une seule fois, soit créer un objet de requête à l&#x27;aide d&#x27;une commande source telle que <code>from</code> ou <code>row</code>, puis enchaîner les méthodes ES|QL pour construire à partir de cet objet.</p><pre><code>query = Elastic::ESQL.from('sample_data')
query.limit(2).sort('@timestamp')</code></pre><p>La gemme traduit le code en ES|QL dans la méthode <code>to_s</code>, de sorte qu&#x27;elle renvoie la requête ES|QL lorsqu&#x27;elle est imprimée ou transformée en chaîne :</p><pre><code>query = Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending
query.to_s
# => "FROM sample_data | LIMIT 2 | SORT @timestamp DESC"</code></pre><p>Vous pouvez instancier un objet de requête et modifier son état initial en utilisant les équivalents <code>!</code> de chaque fonction :</p><pre><code>query = Elastic::ESQL.from('sample_data')
query.to_s
# => "FROM sample_data"
query.limit!(2).sort!('@timestamp')
query.to_s
# => "FROM sample_data | LIMIT 2 | SORT @timestamp"</code></pre><p>L&#x27;outil offre des moyens pratiques d&#x27;enchaîner des étapes supplémentaires à une fonction ES|QL, comme <code>enrich</code> et <code>sort</code>. Une fois que vous avez appelé <code>enrich</code> sur un objet <code>Elastic::ESQL</code>, vous pouvez enchaîner <code>on</code> et <code>with</code>:</p><pre><code>esql.enrich!('policy').on('a').with({ name: 'language_name' })</code></pre><p>Vous pouvez également enchaîner <code>desc</code>, <code>asc</code>, <code>nulls_first</code> et <code>nulls_last</code> à votre requête après avoir utilisé <code>sort</code>:</p><pre><code>Elastic::ESQL.from('sample_data').sort('@timestamp').asc.to_s
# => 'FROM sample_data | SORT @timestamp ASC'

Elastic::ESQL.from('sample_data').sort('@timestamp').desc.nulls_first.to_s
# => 'FROM sample_data | SORT @timestamp DESC NULLS FIRST'</code></pre><p>Elle prend également en charge les chaînes personnalisées, au cas où vous souhaiteriez écrire vous-même la requête ES|QL ou utiliser une fonctionnalité qui n&#x27;a pas encore été ajoutée à la bibliothèque. <code>custom</code> joindra les chaînes à la fin de la requête. Il les ajoutera au fur et à mesure qu&#x27;ils sont envoyés à la fonction, sans ajouter de caractères de liaison. Ils seront combinés au reste de la requête par un caractère d&#x27;espacement.</p><pre><code>esql = Elastic::ESQL.from('sample_data')
esql.custom('| MY_VALUE = "test value"').to_s
# => 'FROM sample_data | MY_VALUE = "test value"'</code></pre><p>Vous pouvez également enchaîner les fonctions de <code>custom</code>:</p><pre><code>esql.custom('| MY_VALUE = "test value"').custom('| ANOTHER, VALUE')
'FROM sample_data | MY_VALUE = "test value" | ANOTHER, VALUE'</code></pre><h2>Utilisation du générateur de requêtes ES|QL avec le client Ruby</h2><p>Vous pouvez utiliser le constructeur de requêtes directement avec <a href="https://github.com/elastic/elasticsearch-ruby">elasticsearch-ruby</a> et l&#x27;API <code>esql.query</code> en envoyant l&#x27;objet de requête :</p><pre><code>require 'elasticsearch'
require 'elastic/esql'

client = Elasticsearch::Client.new
index = 'sample_data'

query = Elastic::ESQL.from(index)
                     .sort('@timestamp')
                     .desc
                     .where('event_duration > 5000000')
                     .limit(3)
                     .eval({ duration_ms: 'ROUND(event_duration/1000000.0, 1)' })
client.esql.query(body: { query: query })</code></pre><p>Vous pouvez également l&#x27;utiliser avec l&#x27;ES|QL Helper du client Elasticsearch Ruby, pour en <a href="https://www.elastic.co/search-labs/blog/esql-ruby-helper-elasticsearch">savoir plus :</a></p><pre><code>require 'elasticsearch/helpers/esql_helper'

Elasticsearch::Helpers::ESQLHelper.query(client, query)</code></pre><h2>En tant qu&#x27;outil autonome</h2><p>La gemme est conçue comme un outil autonome pour construire des requêtes ES|QL de manière idiomatique. Il n&#x27;a aucune dépendance d&#x27;exécution ; vous pouvez l&#x27;utiliser avec le client Ruby officiel d&#x27;Elasticsearch, ou seul.</p><p>La requête générée peut être utilisée avec l&#x27;API <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-query"><code>esql.query</code></a> de toutes les façons dont une application interagit avec l&#x27;API Elasticsearch (Ruby ou non). Une fois qu&#x27;une requête est construite avec <code>elastic-esql</code>, la chaîne générée peut être envoyée à l&#x27;API en tant que paramètre <code>query</code> dans le corps de la requête. </p><p>J&#x27;ai déjà écrit sur l&#x27;<a href="https://www.elastic.co/search-labs/blog/elasticsearch-ruby-tools">utilisation d&#x27;Elasticsearch avec des outils Ruby populaires.</a> Cette gemme peut être utilisée avec n&#x27;importe quel outil Ruby populaire pour interroger Elasticsearch avec ES|QL.</p><h2>Conclusion</h2><p>Cette bibliothèque est en cours de développement et l&#x27;API finale n&#x27;a pas encore été finalisée. Il s&#x27;agit actuellement d&#x27;un aperçu technique. Si vous avez des commentaires sur l&#x27;API actuelle ou sur son utilisation générale, n&#x27;hésitez pas à <a href="https://github.com/elastic/esql-ruby/issues">ouvrir un nouveau dossier.</a> Veuillez vous référer au <a href="https://github.com/elastic/esql-ruby/?tab=readme-ov-file#ruby-esql-query-builder">README</a> pour en savoir plus sur Ruby ES|QL Query Builder.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/vector-search-filtering</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/vector-search-filtering</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Carlos Delgado]]></dc:creator>
            <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03b1dd4c7bda4fbabd8e374bc2e4f12d5be6ef5f-1600x1150.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La recherche vectorielle ne suffit pas pour trouver des résultats pertinents. Il est très courant d&#x27;utiliser des critères de filtrage qui permettent de réduire les résultats de la recherche et d&#x27;é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&#x27;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&#x27;ils répondent également à toutes les exigences nécessaires. L&#x27;expérience de recherche est ainsi beaucoup plus précise, efficace et conviviale.</p><p>C&#x27;est là qu&#x27;Elasticsearch et Apache Lucene excellent - l&#x27;utilisation d&#x27;un filtrage efficace sur différents types de données est l&#x27;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&#x27;effectuer des recherches de vecteurs exacts :</p><ul><li>Utilisation d&#x27;un type d&#x27;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.</li><li>Utilisation d&#x27;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&#x27;importe quel type d&#x27;index.</li></ul><p>Lors de l&#x27;exécution d&#x27;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&#x27;a pas d&#x27;incidence sur la qualité du résultat, car tous les vecteurs sont pris en compte de toute façon. Nous filtrons simplement à l&#x27;avance les résultats qui ne sont pas intéressants, afin de réduire le nombre d&#x27;opérations.</p><p>C&#x27;est très important, car il peut être plus performant d&#x27;exécuter une recherche exacte plutôt qu&#x27;une recherche approximative lorsque les filtres appliqués donnent un petit nombre de documents.</p><p>La règle de base est d&#x27;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&#x27;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&#x27;index <code>flat</code> plutôt qu&#x27;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&#x27;exécution d&#x27;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&#x27;indexation qui sont efficaces pour les trouver et les filtrer, comme les dictionnaires de termes, les listes d&#x27;é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&#x27;obtenir k ou plus après avoir appliqué les filtres.</p><p>L&#x27;avantage du post-filtrage est qu&#x27;il ne modifie pas le comportement de la recherche vectorielle lors de l&#x27;exécution - la recherche vectorielle n&#x27;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&#x27;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><pre><code>{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}</code></pre><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><pre><code>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}</code></pre><p>Gardez à l&#x27;esprit que vous devez utiliser une section de post-filtrage explicite avec la recherche knn. Si vous n&#x27;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&#x27;autres requêtes ou filtres au lieu d&#x27;effectuer un post-filtre.</p><h3>Préfiltrage</h3><p>L&#x27;application de filtres avant la recherche vectorielle permet d&#x27;abord d&#x27;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&#x27;ajouter un candidat aux résultats, il vérifie qu&#x27;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&#x27;il ne s&#x27;agit pas d&#x27;un document valide. L&#x27;efficacité de HNSW repose sur la connexion entre les vecteurs du graphe : si nous cessons d&#x27;explorer un candidat, cela signifie que nous risquons d&#x27;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&#x27;ensuit que l&#x27;application d&#x27;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><pre><code>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}</code></pre><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><pre><code>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}</code></pre><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&#x27;il y a peu de vecteurs à comparer, il est plus rapide d&#x27;effectuer une recherche exacte sur les quelques documents qui satisfont le filtre.</p><p>Il s&#x27;agit d&#x27;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&#x27;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&#x27;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&#x27;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&#x27;Elasticsearch qui spécifie les documents que les rôles d&#x27;utilisateurs peuvent récupérer.</p><p>La DLS est réalisée à l&#x27;aide de requêtes. Une requête peut être associée aux index pour un rôle, ce qui limite effectivement les documents qu&#x27;un utilisateur appartenant à ce rôle peut extraire des index.</p><p>L&#x27;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&#x27;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&#x27;est-à-dire</em>qu&#x27;ils existent dans l&#x27;index et n&#x27;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&#x27;utilisateur seront pris en compte. S&#x27;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&#x27;optimisations.</p><p>Le DLS avec recherche exacte présente les mêmes avantages que l&#x27;application de n&#x27;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&#x27;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&#x27;<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&#x27;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:&#x27;2025-05-28T01:27:58.456Z&#x27;,to:&#x27;2025-06-30T13:53:26.430Z&#x27;))&amp;_a=()">améliorations apportées</a> lors de l&#x27;introduction d&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" style="width: 100%;" /><h2>Conclusion</h2><p>Le filtrage fait partie intégrante de la recherche. S&#x27;assurer que le filtrage est performant dans la recherche vectorielle, et comprendre les compromis et les optimisations, c&#x27;est ce qui fait l&#x27;efficacité et la précision d&#x27;une recherche.</p><p>Le filtrage a un impact sur les performances de la recherche vectorielle :</p><ul><li>La recherche exacte est plus rapide lorsque l&#x27;on utilise le filtrage. Vous pouvez envisager d&#x27;utiliser la recherche exacte au lieu de la recherche approximative si votre filtrage est suffisamment restrictif. Il s&#x27;agit d&#x27;une optimisation automatique dans Elasticsearch.</li><li>La recherche approximative est plus lente lorsque l&#x27;on utilise le préfiltrage. Le préfiltrage nous permet d&#x27;obtenir les k premiers résultats correspondant au filtre, au prix d&#x27;une recherche plus lente.</li><li>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&#x27;il est appliqué.</li></ul><p>Bon filtrage !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Shards et répliques Elasticsearch : Un guide pratique]]></title>
            <description><![CDATA[Maîtriser les concepts de shards et de réplicas Elasticsearch et apprendre à les optimiser.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-shards-and-replicas-guide</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-shards-and-replicas-guide</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
            <pubDate>Thu, 14 Aug 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7775c44f01f2516c4ff4cce6d6bbe9e7b2c38908-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch renforce la puissance de Lucene en construisant un système distribué au-dessus de celui-ci, qui répond aux problèmes d&#x27;évolutivité et de tolérance aux pannes. Il expose également une API REST basée sur JSON, ce qui rend l&#x27;interopérabilité avec d&#x27;autres systèmes très simple.</p><p>Les systèmes distribués comme Elasticsearch peuvent être très complexes, avec de nombreux facteurs qui peuvent affecter leurs performances et leur stabilité. Les <strong>Shards</strong> font partie des concepts les plus fondamentaux d&#x27;Elasticsearch, et la compréhension de leur fonctionnement vous permettra de gérer efficacement un cluster Elasticsearch.</p><p>Cet article explique ce qu&#x27;est un serveur primaire et un serveur réplique, leur impact sur un cluster Elasticsearch et les outils qui permettent de les adapter à des besoins différents.</p><h2>Comprendre les tessons</h2><p>Les données contenues dans un index Elasticsearch peuvent prendre des proportions considérables. Afin de rester gérable, chaque donnée est conservée dans un index, et les index sont un index divisé en un certain nombre de <strong>morceaux.</strong> Chaque tesson Elasticsearch est un index Apache Lucene, chaque index Lucene individuel contenant un sous-ensemble des documents de l&#x27;index Elasticsearch. Le fractionnement des indices de cette manière permet de contrôler l&#x27;utilisation des ressources. Un index Apache Lucene a une limite de 2 147 483 519 (2³¹ - 129) documents.</p><p>Parfois, les indices doivent être déplacés d&#x27;un nœud à l&#x27;autre à des fins de rééquilibrage. Étant donné que ce processus peut être à la fois long et coûteux en ressources, les indices ne doivent pas devenir trop volumineux, ce qui permet de maintenir le temps de récupération à un niveau raisonnable. En outre, comme les indices sont composés de segments Lucene qui doivent être constamment fusionnés, il est important que les segments ne deviennent pas trop grands. Pour ces raisons, Elasticsearch divise les données d&#x27;index en morceaux plus petits et plus faciles à gérer, appelés <strong>shards primaires</strong>, qui peuvent être plus facilement distribués sur un certain nombre de machines. Les shards <strong>répliqués</strong> sont simplement une copie exacte d&#x27;un shard primaire correspondant et nous verrons leur fonction plus loin dans cet article.</p><p>Il est important de disposer d&#x27;un nombre adéquat de fragments pour garantir les performances. Il est donc judicieux de planifier à l&#x27;avance. Lorsque les requêtes sont exécutées en parallèle sur différents nuages, elles s&#x27;exécutent plus rapidement qu&#x27;un index composé d&#x27;un seul nuage, mais uniquement si chaque nuage est situé sur un nœud différent et s&#x27;il y a suffisamment de nœuds dans la grappe. En même temps, cependant, les ensembles consomment de la mémoire et de l&#x27;espace disque, à la fois en termes de données indexées et de métadonnées de grappe. Le fait d&#x27;avoir un trop grand nombre de shards (également appelé oversharding) peut ralentir les requêtes, les demandes d&#x27;indexation et les opérations de gestion, c&#x27;est pourquoi il est essentiel de maintenir un bon équilibre.</p><p>Le nombre de groupes primaires est défini au moment de la création de l&#x27;index <strong>pour cette instance d&#x27;index spécifique</strong>. Si vous avez besoin ultérieurement d&#x27;un nombre différent d&#x27;unités primaires, vous pouvez utiliser les<strong> API de redimensionnement :</strong>division(plus d&#x27;unités<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-shrink">primaires),</a> <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-split">réduction</a> (moins d&#x27;unités primaires) ou <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-clone">clonage</a> (le même nombre d&#x27;unités primaires avec de nouveaux paramètres pour les réplicas). Ces opérations copient des segments Lucene et <strong>évitent une réindexation complète de tous les documents</strong>. Lors de la création d&#x27;un index, vous pouvez définir le nombre de shards primaires et de shards répliqués comme paramètres de l&#x27;index :</p><pre><code>PUT /sensor
{
   "settings" : {
       "index" : {
           "number_of_shards" : 6,
           "number_of_replicas" : 2
       }
   }
}</code></pre><p>(Si vous ne spécifiez pas le nombre de shards ou de répliques, la valeur par défaut est 1, à partir d&#x27;Elasticsearch 7.0). Le nombre idéal d&#x27;unités de stockage doit être déterminé en fonction de la quantité de données contenues dans un index. En règle générale, <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">un fonds optimal doit contenir de 10 à 50 Go de données</a>, avec moins de 200 millions de documents par fonds. Par exemple, si vous prévoyez d&#x27;accumuler environ 300 Go de journaux d&#x27;application par jour, il serait raisonnable d&#x27;avoir environ 10 fichiers dans cet index, à condition que vous disposiez d&#x27;un nombre suffisant de nœuds pour les héberger.</p><p>Au cours de leur vie, les tessons peuvent passer par un certain nombre d&#x27;états, notamment</p><ul><li><strong>Initialisation :</strong> Un état initial avant que le tesson puisse être utilisé.</li><li><strong>Démarré :</strong> État dans lequel le groupe de stockage est actif et peut recevoir des demandes.</li><li><strong>Relocalisation :</strong> Un état qui se produit lorsque les shards sont en train d&#x27;être déplacés vers un autre nœud. Cela peut s&#x27;avérer nécessaire dans certaines conditions, par exemple lorsque le nœud sur lequel ils se trouvent manque d&#x27;espace disque.</li><li><strong>Non assigné :</strong> État d&#x27;un tesson qui n&#x27;a pas été affecté. Une raison est fournie lorsque cela se produit, par exemple, si le nœud hébergeant le dépôt n&#x27;est plus dans le cluster <em>(NODE_LEFT)</em> ou en raison d&#x27;une restauration dans un index fermé <em>(EXISTING_INDEX_RESTORED)</em>.</li></ul><p>Pour afficher tous les shards, leur état et d&#x27;autres métadonnées, vous pouvez utiliser la requête suivante :</p><pre><code>GET _cat/shards</code></pre><p>Pour visualiser les dépôts d&#x27;un index spécifique, vous pouvez ajouter le nom de l&#x27;index à l&#x27;URL, par exemple, sensor :</p><pre><code>GET _cat/shards/sensor</code></pre><p>Cette commande produit une sortie, comme dans l&#x27;exemple suivant. Par défaut, les colonnes affichées comprennent le nom de l&#x27;index, le nom (i.e. ) du dépôt, s&#x27;il s&#x27;agit d&#x27;un dépôt primaire ou d&#x27;une réplique, son état, le nombre de documents, la taille sur le disque, ainsi que l&#x27;adresse IP et l&#x27;ID du nœud où se trouve le dépôt.</p><pre><code>sensor 5 p STARTED    0  283b 127.0.0.1 ziap
sensor 5 r UNASSIGNED                  
sensor 2 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 2 r UNASSIGNED                  
sensor 3 p STARTED    3 7.2kb 127.0.0.1 ziap
sensor 3 r UNASSIGNED                  
sensor 1 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 1 r UNASSIGNED                  
sensor 4 p STARTED    2 3.8kb 127.0.0.1 ziap
sensor 4 r UNASSIGNED                  
sensor 0 p STARTED    0  283b 127.0.0.1 ziap
sensor 0 r UNASSIGNED</code></pre><h2>Comprendre les répliques</h2><p>Alors que chaque nuage contient une seule copie des données, un index peut contenir plusieurs copies du nuage. Il y a donc deux types de tessons, le <strong>tesson primaire</strong> et une copie, ou <strong>réplique</strong>. Chaque réplique d&#x27;un groupe de données primaire est toujours située sur un nœud différent, ce qui garantit la haute disponibilité de vos données en cas de défaillance d&#x27;un nœud. Outre la redondance et leur rôle dans la prévention des pertes de données et des temps d&#x27;arrêt, les répliques peuvent également contribuer à améliorer les performances de recherche en permettant aux requêtes d&#x27;être traitées en parallèle avec le shard principal, et donc plus rapidement.</p><p>Il existe des différences importantes dans la manière dont se comportent les disques primaires et les disques répliques. Bien qu&#x27;ils soient tous deux capables de traiter les requêtes, les demandes d&#x27;indexation (c.-à-d. les demandes d&#x27;accès à la base de données) ne sont pas traitées. l&#x27;ajout de données à l&#x27;index) doivent d&#x27;abord passer par les disques primaires avant d&#x27;être répliqués dans les disques répliques. Comme nous l&#x27;avons vu plus haut, si un shard primaire devient indisponible, par exemple en raison d&#x27;une déconnexion de nœud ou d&#x27;une défaillance matérielle, un réplica est promu pour reprendre son rôle.</p><p>Si les répliques peuvent être utiles en cas de défaillance d&#x27;un nœud, il est important de ne pas en avoir trop, car elles consomment de la mémoire, de l&#x27;espace disque et de la puissance de calcul lors de l&#x27;indexation. Une autre différence entre les shards primaires et les réplicas est que le nombre de shards primaires ne peut pas être modifié après la création de l&#x27;index, alors que le nombre de réplicas peut être modifié dynamiquement à tout moment en mettant à jour les paramètres de l&#x27;index.</p><p>Un autre facteur à prendre en compte pour les répliques est le nombre de nœuds disponibles. Les répliques sont toujours placées sur des nœuds différents de ceux du groupe principal, car deux copies des mêmes données sur le même nœud n&#x27;offriraient aucune protection en cas de défaillance de ce nœud. Par conséquent, pour qu&#x27;un système prenne en charge <em>n</em> répliques, il doit y avoir au moins <em>n + 1</em> nœuds dans la grappe. Par exemple, s&#x27;il y a deux nœuds dans un cluster et qu&#x27;un index est configuré avec six répliques, une seule réplique sera allouée. En revanche, un système à sept nœuds est parfaitement capable de gérer un shard primaire et six répliques.</p><h2>Optimisation des grappes et des répliques</h2><p>Même après la création d&#x27;un index avec le bon équilibre entre les unités primaires et les unités répliquées, celles-ci doivent être surveillées, car la dynamique autour d&#x27;un index évolue au fil du temps. Par exemple, lorsqu&#x27;il s&#x27;agit de séries chronologiques, les indices contenant des données récentes sont généralement plus actifs que les indices plus anciens. Sans réglage de ces indices, ils consommeraient tous la même quantité de ressources, malgré leurs exigences très différentes.</p><p>L&#x27;API de l&#x27;indice de reconduction peut être utilisée pour séparer les indices les plus récents des plus anciens. Il peut être configuré pour créer automatiquement un nouvel index lorsqu&#x27;un certain seuil - taille d&#x27;un index sur le disque, nombre de documents ou âge - est atteint. Cette API est également utile pour contrôler la taille des fichiers. Étant donné que le nombre de groupes ne peut pas être facilement modifié après la création de l&#x27;index, les groupes continueront d&#x27;accumuler des données si aucune condition de transfert n&#x27;est remplie. Pour les index plus anciens qui ne nécessitent que des accès peu fréquents, le rétrécissement et la fusion forcée d&#x27;un index sont deux moyens différents de réduire leur empreinte mémoire et disque. La première permet de réduire le nombre d&#x27;unités dans un index, tandis que la seconde réduit le nombre de segments Lucene et libère l&#x27;espace utilisé par les documents qui ont été supprimés.</p><h2>Shards primaires et répliques comme base d&#x27;Elasticsearch</h2><p>Elasticsearch s&#x27;est forgé une solide réputation en tant que plateforme distribuée de stockage, de recherche et d&#x27;analyse pour d&#x27;énormes volumes de données. Toutefois, à une telle échelle, des problèmes se posent inévitablement. C&#x27;est pourquoi il est si important et fondamental pour Elasticsearch de comprendre le fonctionnement des shards primaires et répliqués, car cela permet d&#x27;optimiser la fiabilité et les performances de la plateforme.</p><p>Il est essentiel de savoir comment ils fonctionnent et comment les optimiser pour obtenir un cluster Elasticsearch plus robuste et plus performant. Si vous rencontrez régulièrement des réponses lentes aux requêtes ou des pannes, ces connaissances peuvent être la clé pour surmonter ces obstacles.</p><p>Suivez la documentation officielle d&#x27;Elasticsearch pour en savoir plus sur les <a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/clusters-nodes-shards">clusters, les nœuds et les shards</a>, sur <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">la taille des shards</a>, sur l <a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/shard-allocation-relocation-recovery">&#x27;allocation des shards et sur la récupération.</a></p><p>Ce sujet est également disponible sous forme de cours d&#x27;introduction sur la <a href="https://youtu.be/sAySPSyL2qE">chaîne YouTube de la communauté Elastic</a>.</p><p>Enfin, si vous ne voulez pas vous préoccuper des nœuds, des unités de stockage ou des répliques, vous pouvez essayer <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/serverless">Elastic Cloud Serverless</a>. Cette offre Elastic Cloud est entièrement gérée par Elastic et automatisée pour évoluer avec votre charge de travail. Un essai gratuit peut vous aider à vous familiariser avec d&#x27;autres avantages de l&#x27;approche sans serveur.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment afficher les champs d'un index Elasticsearch ?]]></title>
            <description><![CDATA[Apprenez à afficher les champs d'un index Elasticsearch à l'aide des API _mapping et _search, des sous-champs, de la _source synthétique et des champs d'exécution.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-index-show-fields</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-index-show-fields</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Wed, 06 Aug 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/23b96aaa1a38b1f4747b4a87695d816f24c0cf70-720x421.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans cet article, nous verrons comment afficher les champs d&#x27;un index Elasticsearch. Cela peut être utile pour comprendre la structure de vos données, identifier des champs spécifiques et résoudre des problèmes. Nous aborderons les sujets suivants :</p><ol><li>Utilisation de l&#x27;API <code>_mapping</code> pour récupérer des informations sur les champs</li><li>Utilisation de l&#x27;API <code>_search</code> pour afficher les valeurs des champs</li><li>Affichage des sous-champs</li><li>Synthetic _source</li><li>Champs d&#x27;exécution</li></ol><h2>1. Utilisation de l&#x27;API _mapping pour récupérer des informations sur les champs</h2><p>L&#x27;API <code>_mapping</code> vous permet de récupérer la définition du mappage pour un ou plusieurs index. Il s&#x27;agit d&#x27;informations sur les champs, leurs types de données et d&#x27;autres propriétés. Pour récupérer le mappage d&#x27;un index spécifique, utilisez la requête suivante :</p><pre><code>GET /<index_name>/_mapping</code></pre><p>Par exemple, si vous avez un index nommé <code>my_index</code>, vous pouvez récupérer son mapping avec la requête suivante :</p><pre><code>GET /my_index/_mapping</code></pre><p>La réponse comprendra la définition du mappage pour l&#x27;index, qui contient des informations sur les champs et leurs propriétés.</p><p>Il est également possible de récupérer la cartographie d&#x27;un champ spécifique. Cela peut s&#x27;avérer utile si votre cartographie est assez vaste et que vous souhaitez vous concentrer sur un domaine spécifique. Pour récupérer la correspondance d&#x27;un champ spécifique, utilisez la requête suivante :</p><pre><code>GET /my_index/_mapping/field/my_field</code></pre><p>Vous pouvez également récupérer les correspondances de plusieurs champs en séparant leurs noms par des virgules, comme dans la requête suivante :</p><pre><code>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3</code></pre><h2>2. Utilisation de l&#x27;API _search pour afficher les valeurs des champs</h2><p>Pour afficher les valeurs des champs d&#x27;un index Elasticsearch, vous pouvez utiliser l&#x27;API <code>_search</code>. L&#x27;API <code>_search</code> vous offre plusieurs moyens de contrôler les champs renvoyés ; les deux principaux sont les suivants :</p><ol><li><strong><code>_source</code></strong>: Le champ <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field"><code>_source</code></a> contient le corps du document JSON original tel qu&#x27;il a été indexé, y compris les modifications apportées par les pipelines d&#x27;ingestion ou les étapes de prétraitement. Pour afficher des champs spécifiques du document source, il faut mettre en œuvre le filtrage de la source, comme nous le verrons ci-dessous.</li><li><strong><code>fields</code></strong>: Le paramètre <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields"><code>fields</code></a> vous permet d&#x27;extraire des champs spécifiques de vos documents lors d&#x27;une recherche, sur la base du mappage de l&#x27;index. Contrairement à <code>_source</code>, <code>fields</code> peut également renvoyer des valeurs provenant de champs stockés, de valeurs documentaires ou de champs d&#x27;exécution sans faire référence à <code>_source</code>, bien que pour les champs standard sans valeurs documentaires ou paramètres stockés, il se réfère à <code>_source</code>. Cela peut apporter de nombreux avantages, notamment en termes de performances, comme nous le verrons ci-dessous.</li></ol><h3>Utilisation du champ _source<code> </code></h3><p>Par défaut, l&#x27;API<code> _search</code> renvoie le champ <code>_source</code>, qui contient le document JSON original qui a été indexé. Pour afficher des champs spécifiques, vous pouvez ajouter des filtres dans le paramètre <code>_source </code>de la demande de recherche ; c&#x27;est ce qu&#x27;on appelle le filtrage à la source.</p><p>Voici un exemple de demande de recherche qui renvoie les valeurs des champs <code>title </code>et <code>author</code> pour les documents de l&#x27;index <code>my_index</code>:</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}</code></pre><p>Dans cet exemple, le paramètre <code>_source</code> spécifie les champs à renvoyer.</p><p>Si vous avez besoin d&#x27;encore plus de contrôle, vous pouvez utiliser les propriétés <code>includes</code> et <code>excludes </code>de l&#x27;objet <code>_source</code>. Par exemple, la requête ci-dessous renvoie le champ de premier niveau <code>title</code> et tous les sous-champs de <code>author</code> à l&#x27;exception de <code>author.description</code>.</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": {
     “includes”: [“title”, “author.*],
     “excludes”: [“author.description”]
  }
}</code></pre><p>Dans cet exemple, nous utilisons le modèle <code>author.* </code>pour récupérer tous les sous-champs directs de l&#x27;objet <code>author </code>. Nous excluons ensuite explicitement <code>author.description </code>afin que seuls les autres champs relatifs à l&#x27;auteur soient renvoyés. Notez que cela n&#x27;améliore pas les performances, puisqu&#x27;il faut toujours charger et analyser la source JSON, mais cela permet de réduire la taille de la réponse envoyée sur le réseau.</p><h3>Utilisation du paramètre champs</h3><p>Vous pouvez utiliser le paramètre <code>fields</code> pour filtrer les champs renvoyés dans la réponse de recherche. L&#x27;utilisation de <code>fields</code> par rapport à <code>_source</code> présente plusieurs avantages, notamment</p><ul><li><strong>Amélioration des performances : </strong><code>fields </code>peut renvoyer des valeurs directement à partir de <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-store">champs stockés</a> ou de <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/doc-values">valeurs de documents</a> sans avoir à charger l&#x27;intégralité du site <code>_source</code>, ce qui réduit la taille de la charge utile de la réponse.</li><li><strong>Sortie formatée :</strong> Pour les champs standard,<code> fields</code> peut se référer à <code>_source</code> pour récupérer les valeurs, mais il s&#x27;appuie sur le mappage de l&#x27;index pour formater correctement la sortie, comme les dates formatées, afin de les rendre cohérentes avec ce qui est utilisé pour les agrégations et les tris.</li><li><strong>Accès aux champs d&#x27;exécution :</strong> <code>fields</code> peut renvoyer des champs d&#x27;exécution qui n&#x27;existent pas sur le site original <code>_source</code>.</li><li>D&#x27;autres avantages peuvent être trouvés <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#search-fields-param">ici.</a></li></ul><p>Par exemple, pour obtenir uniquement les champs <code>title</code> et <code>author</code> dans l&#x27;index <code>my_index</code>, vous pouvez utiliser la requête de recherche suivante :</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}</code></pre><p>Dans la requête ci-dessus, nous attribuons la valeur false au champ <code>_source </code>afin de ne pas renvoyer le document source. Cela peut réduire considérablement la taille de la charge utile de la réponse, mais n&#x27;oubliez pas que cela ne fonctionne que si les champs <code>title</code> et <code>author</code> sont de type <code>keyword </code>, pour lesquels <code>doc_values</code> est activé par défaut. Si le champ n&#x27;a pas été activé par <code>doc_values</code> et que <code>_source</code> a été défini sur false, Elasticsearch n&#x27;aura aucun moyen de les récupérer et ils seront ignorés dans la réponse.</p><p>Il est important de noter que la réponse <code>fields</code> renvoie toujours un tableau de valeurs pour chaque champ, même s&#x27;il n&#x27;y a qu&#x27;une seule valeur. Cela est dû au fait qu&#x27;Elasticsearch n&#x27;a pas de type de tableau dédié, et que tout champ peut avoir plusieurs valeurs. Pour plus d&#x27;informations sur les tableaux dans Elasticsearch, cliquez <a href="http://elastic.co/docs/reference/elasticsearch/mapping-reference/array">ici.</a></p><h3>Autres moyens d&#x27;extraire des champs</h3><p>Bien que l&#x27;extraction de champs à l&#x27;aide de <code>_source</code> ou <code>fields</code> soit la méthode recommandée, il existe d&#x27;autres méthodes pour des cas d&#x27;utilisation spécifiques, comme par exemple :</p><p><strong>Champs de valeur du document :</strong> Si vous souhaitez éviter <code>_source</code>, vous pouvez effectuer une recherche en utilisant le paramètre <code><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#docvalue-fields">docvalue_fields</a> </code> . Doc values stocke les mêmes valeurs de champ que <code>_source</code> mais dans une structure de données sur disque, optimisée pour les tris et les agrégations.</p><p>Comme il s&#x27;agit d&#x27;une valeur distincte des valeurs stockées sur <code>_source</code>, vous pouvez demander des champs spécifiques sans avoir à charger l&#x27;ensemble du site <code>_source</code>. Cette option est utile si vous interrogez des documents volumineux, mais que vous n&#x27;avez besoin que de quelques petits champs prenant en charge des valeurs de documents. Un autre cas d&#x27;utilisation de <code>docvalue_fields </code>est celui où vous souhaitez utiliser un formatage personnalisé pour les champs <code>date</code> et <code>numeric</code>, comme nous le verrons dans l&#x27;exemple ci-dessous.</p><p>Notez que cela ne fonctionne que pour les champs pour lesquels vous avez activé <code>doc_values</code> ou pour les types de champs pour lesquels cette option est activée par défaut, tels que <code>keyword</code>, <code>date</code>, les types numériques et <code>boolean</code>, et non pour <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/text"><code>text</code></a> ou <a href="https://www.elastic.co/docs/reference/elasticsearch/plugins/mapper-annotated-text-usage"><code>annotated_text</code></a>.</p><p>Dans cet exemple, nous utilisons le paramètre <code>docvalue_fields</code> pour récupérer les champs <code>title</code>, <code>author</code> et <code>published</code> sans charger le document <code>_source</code> complet :</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "docvalue_fields": [
    "title",
    "author",
    {
      "field": "published",
      "format": "epoch_millis"
    }
  ],
  "_source": false
}</code></pre><p>Lorsque cette requête est exécutée, Elasticsearch récupère les valeurs directement à partir de son magasin en colonnes sur disque au lieu de référencer le site <code>_source </code>pour chaque document. Le champ <code>published</code> est retourné avec le format <code>epoch_millis</code> au lieu du format par défaut, grâce au paramètre <code>format</code> fourni dans la requête.</p><p><strong>Champs stockés :</strong> Si vous avez explicitement marqué des champs spécifiques comme étant <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-store">stockés</a> dans le mappage, vous pouvez utiliser le paramètre <code>stored_fields</code> pour filtrer ces champs. C&#x27;est utile si vous voulez des réponses légères avec seulement ces champs spécifiques ou pour les champs que vous avez délibérément stockés pour les retrouver plus tard. Il est stocké séparément de <code>_source</code>, de sorte que cette méthode est également utile pour éviter de devoir charger <code>_source</code>.</p><p>Il est important de noter que cette option est désactivée par défaut et qu&#x27;elle n&#x27;est généralement pas recommandée. Utilisez plutôt le filtrage des sources pour renvoyer certains sous-ensembles du document source original.</p><p>Dans l&#x27;exemple de requête ci-dessous, nous utilisons le paramètre <code>stored_fields</code> pour récupérer le champ <code>summary</code>, dont la configuration de mappage d&#x27;index est &quot;<code>store”: true</code>.</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "stored_fields": ["summary"]
}</code></pre><p>Lorsque cette requête est exécutée, Elasticsearch vérifie si ce champ a été marqué par <code>”store”: true</code>, s&#x27;il ne le trouve pas, il l&#x27;ignore complètement.</p><h2>3. Affichage des sous-champs</h2><p>Si votre index contient des sous-champs, vous pouvez utiliser la notation point pour spécifier le chemin d&#x27;accès au champ dans le paramètre <code>fields</code>. Notez que les sous-champs sont différents du <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/nested">type de champ imbriqué</a>. Par exemple, si vous avez un sous-champ nommé <code>address.city</code>, vous pouvez l&#x27;inclure dans la réponse de recherche comme suit :</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}</code></pre><p>Dans cet exemple, la réponse de la recherche comprendra les valeurs des champs <code>title</code>, <code>author</code> et <code>address.city</code>.</p><h2>4. Synthétique _source</h2><p>Si vous souhaitez conserver la fonctionnalité de<code> _source</code> tout en économisant de l&#x27;espace disque, vous avez la possibilité d&#x27;utiliser le site synthétique <code>_source</code> dans votre mappage d&#x27;index. <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field#synthetic-source">Synthetic <code>_source</code></a> est une fonctionnalité qui permet à Elasticsearch de reconstruire <code>_source</code> à partir de données existantes telles que des champs stockés et des valeurs de documents, même lorsque <code>_source</code> est désactivé. Cela vous permet d&#x27;économiser beaucoup d&#x27;espace de stockage au prix d&#x27;une vitesse légèrement inférieure au moment de l&#x27;interrogation, car la reconstruction se fait à la volée. Activez cette fonction en utilisant les valeurs ci-dessous dans vos paramètres d&#x27;index :</p><pre><code>PUT idx
{
  "settings": {
    "index": {
      "mapping": {
        "source": {
          "mode": "synthetic"
        }
      }
    }
  }
}</code></pre><p>Parmi les avantages de l&#x27;utilisation de la version synthétique de <code>_source </code>, citons : l&#x27;affichage complet du document lors de l&#x27;utilisation de l&#x27;API <code>_search</code>, le filtrage des sources et la compatibilité avec d&#x27;autres fonctionnalités et outils tels que Kibana qui s&#x27;attendent à ce que <code>_source</code> soit disponible, tout en évitant d&#x27;avoir à stocker le document <code>_source</code> dans son intégralité.</p><h2>5. Champs d&#x27;exécution</h2><p>Les champs <a href="https://www.elastic.co/docs/manage-data/data-store/mapping/runtime-fields">d&#x27;exécution</a> vous permettent de définir des champs scriptés au moment de la requête ou dans votre mappage d&#x27;index sous un bloc d&#x27;exécution. Ces champs ne sont jamais indexés, de sorte que l&#x27;ajout d&#x27;un champ d&#x27;exécution n&#x27;augmente pas la taille de l&#x27;index mais n&#x27;apparaîtra jamais dans <code>_source</code>. Les champs d&#x27;exécution définis dans le mappage sont persistants et disponibles pour toutes les requêtes, tandis que les champs d&#x27;exécution définis au moment de la requête sont temporaires et ne sont disponibles que dans cette requête de recherche.</p><p>Le principal avantage de l&#x27;utilisation des champs d&#x27;exécution est la possibilité d&#x27;ajouter des champs aux documents après les avoir ingérés, ce qui simplifie vos décisions en matière de mappage. Les champs d&#x27;exécution sont également très utiles pour enrichir vos documents avec des valeurs qui n&#x27;existent pas dans le document original mais qui sont générées à l&#x27;aide d&#x27;un script, comme le formatage d&#x27;une chaîne de caractères ou le calcul d&#x27;un score.</p><p>Il convient également de noter que les champs d&#x27;exécution peuvent nuire aux performances, car un script devra être exécuté pour chaque document de l&#x27;ensemble des résultats. Pour <a href="https://www.elastic.co/docs/manage-data/data-store/mapping/retrieve-runtime-field">récupérer un champ d&#x27;exécution</a>, vous pouvez également utiliser le paramètre <code>fields</code> de l&#x27;API <code>_search</code>.</p><h2>Conclusion</h2><p>L&#x27;affichage des champs d&#x27;un index Elasticsearch peut aller de la simple récupération des valeurs à l&#x27;aide du mappage d&#x27;index ou de <code>_source</code>, à des méthodes plus avancées utilisant <code>fields</code>, <code>docvalue_fields</code>, ou des champs d&#x27;exécution pour un meilleur contrôle et une plus grande efficacité. Il est essentiel de comprendre les compromis entre les différentes méthodes pour optimiser vos expériences de recherche. Qu&#x27;il s&#x27;agisse d&#x27;optimiser les charges utiles, d&#x27;enrichir des documents ou d&#x27;utiliser le site synthétique <code>_source</code> pour économiser de l&#x27;espace de stockage, Elasticsearch vous offre de nombreux outils et fonctionnalités pour trouver les données dont vous avez besoin, de la manière dont vous en avez besoin. Ces techniques peuvent vous aider à comprendre la structure de vos données, à identifier des champs spécifiques et à résoudre des problèmes.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tableaux de bord alimentés par l'IA : D'une vision à Kibana]]></title>
            <description><![CDATA[Générer un tableau de bord en utilisant un LLM pour traiter une image et la transformer en tableau de bord Kibana.
]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/ai-powered-dashboards</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/ai-powered-dashboards</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo, Tomás Murúa]]></dc:creator>
            <pubDate>Wed, 16 Jul 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb60ceb2fbc3941745b21ae3357cbb6ea8fab18c-1443x811.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kibana/kibana-lens">Kibana Lens</a> simplifie le glisser-déposer des tableaux de bord, mais lorsque vous avez besoin de dizaines de panneaux, les clics s&#x27;accumulent. Et si vous pouviez dessiner un tableau de bord, en faire une capture d&#x27;écran et laisser un LLM terminer tout le processus à votre place ?</p><p>Dans cet article, nous allons y parvenir. Nous allons créer une application qui prend une image d&#x27;un tableau de bord, analyse nos mappings et génère un tableau de bord sans que nous ayons à toucher à Kibana !</p><p><strong>Les étapes</strong>:</p><ol><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#background-&amp;-application-workflow">Contexte &amp; flux de travail de l&#x27;application</a></li><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#prepare-data">Préparer les données</a></li><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#llm-configuration">Configuration LLM</a></li><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#application-functions">Fonctions d&#x27;application</a></li></ol><h2>Contexte &amp; flux de travail de l&#x27;application</h2><p>La première idée qui m&#x27;est venue à l&#x27;esprit a été de laisser le LLM générer l&#x27;ensemble des <a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/saved-objects">objets sauvegardés au</a> format NDJSON dans Kibana, puis de les importer dans Kibana.</p><p>Nous avons essayé une poignée de modèles :</p><ul><li>Gemini 2.5 pro</li><li>GPT o3 / o4-mini-high / 4.1</li><li>Sonnet de Claude 4</li><li>Grok 3</li><li>Deepseek (Deepthink R1)</li></ul><p>En ce qui concerne les messages-guides, nous avons commencé par une phrase simple :</p><pre><code>You are an Elasticsearch Saved-Object generator (Kibana 9.0).
INPUTS
=====
1. PNG screenshot of a 4-panel dashboard (attached).
2. Index mapping (below) – trimmed down to only the fields present in the screenshot.
3. Example NDJSON of *one* metric visualization (below) for reference.

TASK
====
Return **only** a valid NDJSON array that recreates the dashboard exactly:
* 2 metric panels (Visits, Unique Visitors)
* 1 pie chart (Most used OS)
* 1 vertical bar chart (State Geo Dest)
* Use index pattern `kibana_sample_data_logs`.
* Preserve roughly the same layout (2×2 grid).
* Use `panelIndex` values 1-4 and random `id` strings.
* Kibana version: 9.0</code></pre><p>Bien que nous ayons parcouru des <a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic#:~:text=Few%2Dshot%20prompting%20involves%20providing%20examples%20of%20the%20types%20of%20queries%20you%20want%20it%20to%20return%2C%20which%20helps%20in%20increasing%20consistency.">exemples en quelques images</a> et des explications détaillées sur la manière de construire chaque visualisation, nous n&#x27;avons pas eu de chance. Si vous êtes intéressé par cette expérimentation, vous pouvez trouver des détails <a href="https://gist.github.com/TomasMurua/a78dc283e115624731beffc98984b70b">ici.</a></p><p>Le résultat de cette approche était l&#x27;apparition de ces messages lorsque l&#x27;on essayait de télécharger vers Kibana les fichiers produits par le LLM :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2b599443b5613c9f0fc3235581614add5b4b3900-891x98.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d87ccfc033bc00ee8188c5cae18043fbca22784c-741x233.png" style="width: 100%;" /><p>Cela signifie que le JSON généré est invalide ou mal formaté. Les problèmes les plus fréquents étaient que le LLM produisait des NDJSON incomplets, des paramètres hallucinants ou retournait du JSON normal au lieu de NDJSON, même si nous essayions de faire en sorte qu&#x27;il en soit autrement.</p><p>Inspirés par <a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">cet article</a> - où les <a href="https://www.elastic.co/docs/solutions/search/search-templates">modèles de recherche</a> ont mieux fonctionné que le LLM freestyle - nous avons décidé de donner des modèles au LLM au lieu de demander de générer le fichier NDJSON complet et ensuite nous, dans le code, utilisons les paramètres donnés par le LLM pour créer les visualisations appropriées.</p><p>Le processus de candidature sera le suivant :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/52c587cf0cf3517fdd4ee7ab95581dd4f2bce030-725x668.png" style="width: 100%;" /><p></p><p><em>Nous omettons une partie du code pour des raisons de simplicité, mais vous pouvez trouver le code de travail de l&#x27;application complète sur <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/from-image-idea-to-kibana-dashboard-using-ai.ipynb"><strong>ce</strong></a> carnet.</em></p><h2>Produits requis</h2><p>Avant de commencer à développer, vous aurez besoin des éléments suivants :</p><ol><li>Python 3.8 ou supérieur</li><li>Un environnement <a href="https://docs.python.org/3/library/venv.html">Venv</a> Python</li><li>Une instance Elasticsearch en cours d&#x27;exécution, ainsi que son point d&#x27;accès et sa clé API</li><li>Une clé d&#x27;API OpenAI stockée dans la variable d&#x27;environnement OPENAI_API_KEY :</li></ol><pre><code>export OPENAI_API_KEY="your-openai-api-key"</code></pre><h2>Préparer les données</h2><p>Pour les données, nous resterons simples et utiliserons les journaux web de l&#x27;échantillon Elastic. Pour savoir comment importer ces données dans votre cluster <a href="https://www.elastic.co/docs/manage-data/ingest/sample-data#add-sample-data-sets">, cliquez ici.</a></p><p>Chaque document contient des informations sur l&#x27;hôte qui a envoyé des demandes à l&#x27;application, ainsi que des informations sur la demande elle-même et l&#x27;état de sa réponse. Vous trouverez ci-dessous un exemple de document :</p><pre><code>{
    "agent": "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24",
    "bytes": 8509,
    "clientip": "70.133.115.149",
    "extension": "css",
    "geo": {
        "srcdest": "US:IT",
        "src": "US",
        "dest": "IT",
        "coordinates": {
            "lat": 38.05134111,
            "lon": -103.5106908
        }
    },
    "host": "cdn.elastic-elastic-elastic.org",
    "index": "kibana_sample_data_logs",
    "ip": "70.133.115.149",
    "machine": {
        "ram": 5368709120,
        "os": "osx"
    },
    "memory": null,
    "message": "70.133.115.149 - - [2018-08-30T23:35:31.492Z] \"GET /styles/semantic-ui.css HTTP/1.1\" 200 8509 \"-\" \"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24\"",
    "phpmemory": null,
    "referer": "http://twitter.com/error/john-phillips",
    "request": "/styles/semantic-ui.css",
    "response": 200,
    "tags": [
        "success",
        "info"
    ],
    "@timestamp": "2025-07-03T23:35:31.492Z",
    "url": "https://cdn.elastic-elastic-elastic.org/styles/semantic-ui.css",
    "utc_time": "2025-07-03T23:35:31.492Z",
    "event": {
        "dataset": "sample_web_logs"
    },
    "bytes_gauge": 8509,
    "bytes_counter": 51201128
}</code></pre><p>Prenons maintenant les mappings de l&#x27;index que nous venons de charger, <code>kibana_sample_data_logs</code>:</p><pre><code>INDEX_NAME = "kibana_sample_data_logs"

es_client = Elasticsearch(
    [os.getenv("ELASTICSEARCH_URL")],
    api_key=os.getenv("ELASTICSEARCH_API_KEY"),
)

result = es_client.indices.get_mapping(index=INDEX_NAME)
index_mappings = result[list(result.keys())[0]]["mappings"]["properties"]</code></pre><p>Nous allons transmettre les mappings avec l&#x27;image que nous chargerons plus tard.</p><h2>Configuration LLM</h2><p>Configurons le LLM pour qu&#x27;il utilise la <a href="https://python.langchain.com/docs/concepts/structured_outputs/">sortie structurée</a> afin d&#x27;entrer une image et de recevoir un JSON contenant les informations que nous devons transmettre à notre fonction pour produire les objets JSON.</p><p>Nous installons les dépendances :</p><pre><code>pip install elasticsearch pydantic langchain langchain-openai -q</code></pre><p>Elasticsearch nous aidera à récupérer les <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">mappages d&#x27;index</a>. Pydantic nous permet de définir des schémas en Python pour demander au LLM de les suivre, et <a href="https://www.elastic.co/search-labs/integrations/langchain">LangChain</a> est le cadre qui facilite l&#x27;appel aux LLM et aux outils d&#x27;IA.</p><p>Nous allons créer un schéma pydantique pour définir les résultats que nous voulons obtenir du LLM. Ce que nous devons savoir à partir de l&#x27;image, c&#x27;est le type de graphique, le champ, le titre de la visualisation et le titre du tableau de bord :</p><pre><code>class Visualization(BaseModel):
    title: str = Field(description="The dashboard title")
    type: List[Literal["pie", "bar", "metric"]]
    field: str = Field(
        description="The field that this visualization use based on the provided mappings"
    )


class Dashboard(BaseModel):
    title: str = Field(description="The dashboard title")
    visualizations: List[Visualization]</code></pre><p>Pour la saisie de l&#x27;image, nous enverrons un tableau de bord que je viens de dessiner :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/36441d7b5dc1f3ff2ac2a30710208d57ad41c716-1600x898.jpg" style="width: 100%;" /><p>Nous déclarons maintenant l&#x27;appel au modèle LLM et le chargement de l&#x27;image. Cette fonction recevra les mappings de l&#x27;index Elasticsearch et une image du tableau de bord que nous voulons générer.</p><p>Avec <code>with_structured_output</code>, nous pouvons utiliser notre schéma Pydantic <code>Dashboard</code> comme objet de réponse que le LLM produira. Avec <a href="https://docs.pydantic.dev/latest/">Pydantic</a>, nous pouvons définir des modèles de données avec validation, ce qui garantit que la sortie LLM correspond à la structure attendue.</p><p>Pour convertir l&#x27;image en base64 et l&#x27;envoyer en entrée, vous pouvez utiliser un <a href="https://www.base64-image.de/">convertisseur en ligne</a> ou le faire <a href="https://www.geeksforgeeks.org/python-convert-image-to-string-and-vice-versa/">en code</a>.</p><pre><code>prompt = f"""
    You are an expert in analyzing Kibana dashboards from images for the version 9.0.0 of Kibana.

    You will be given a dashboard image and an Elasticsearch index mapping.

    Below are the index mappings for the index that the dashboard is based on.
    Use this to help you understand the data and the fields that are available.

    Index Mappings:
    {index_mappings}

    Only include the fields that are relevant for each visualization, based on what is visible in the image.
    """

message = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": prompt},
            {
                "type": "image",
                "source_type": "base64",
                "data": image_base64,
                "mime_type": "image/png",
            },
        ],
    }
]


try:
    llm = init_chat_model("gpt-4.1-mini")
    llm = llm.with_structured_output(Dashboard)
    dashboard_values = llm.invoke(message)

    print("Dashboard values generated by the LLM successfully")
    print(dashboard_values)
except Exception as e:
    print(f"Failed to analyze image and match fields: {str(e)}")</code></pre><p>Le LLM connaît déjà le contexte des tableaux de bord Kibana, nous n&#x27;avons donc pas besoin de tout expliquer dans l&#x27;invite, juste quelques détails pour s&#x27;assurer qu&#x27;il n&#x27;oublie pas qu&#x27;il travaille avec Elasticsearch et Kibana.</p><p>Décortiquons l&#x27;invitation :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Section</td><td style="border: 1px solid #ccc; padding: 4px;">Raison</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Vous êtes un expert en analyse de tableaux de bord Kibana à partir d'images pour la version 9.0.0 de Kibana.</td><td style="border: 1px solid #ccc; padding: 4px;">En insistant sur le fait qu'il s'agit d'Elasticsearch et de la version d'Elasticsearch, nous réduisons la probabilité que le LLM hallucine des paramètres anciens/invalides.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Vous recevrez une image de tableau de bord et un mappage d'index Elasticsearch.</td><td style="border: 1px solid #ccc; padding: 4px;">Nous expliquons que l'image concerne les tableaux de bord afin d'éviter toute interprétation erronée de la part du LLM.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Vous trouverez ci-dessous les correspondances d'index pour l'index sur lequel le tableau de bord est basé, ce qui vous aidera à comprendre les données et les champs disponibles. Mappages d'index : {index_mappings}</td><td style="border: 1px solid #ccc; padding: 4px;">Il est essentiel de fournir les correspondances afin que le LLM puisse sélectionner les champs valides de manière dynamique. Sinon, nous pourrions coder en dur les correspondances ici, ce qui est trop rigide, ou compter sur le fait que l'image contienne les bons noms de champs, ce qui n'est pas fiable.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">N'incluez que les champs pertinents pour chaque visualisation, en fonction de ce qui est visible dans l'image.</td><td style="border: 1px solid #ccc; padding: 4px;">Nous avons dû ajouter ce renforcement parce qu'il arrive que l'on essaie d'ajouter des champs qui ne sont pas pertinents pour l'image.</td>
                    </tr>
              </tbody>
            </table>
          <p>Cela renvoie un objet contenant un tableau de visualisations à afficher :</p><pre><code>"Dashboard values generated by the LLM successfully
title=""Client, Extension, OS, and Response Keyword Analysis""visualizations="[
   "Visualization(title=""Count of Client IP",
   "type="[
      "metric"
   ],
   "field=""clientip"")",
   "Visualization(title=""Extension Keyword Distribution",
   "type="[
      "pie"
   ],
   "field=""extension.keyword"")",
   "Visualization(title=""Most Used OS",
   "type="[
      "bar"
   ],
   "field=""machine.os.keyword"")",
   "Visualization(title=""Response Keyword Distribution",
   "type="[
      "bar"
   ],
   "field=""response.keyword"")"
]</code></pre><h2>Traitement de la réponse au mécanisme d&#x27;apprentissage tout au long de la vie</h2><p>Nous avons créé un exemple de tableau de bord 2x2 panneaux à l&#x27;adresse<strong> </strong>et l&#x27;avons exporté en JSON à l&#x27;aide de l&#x27;<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-dashboards-dashboard">API Get a dashboard</a>, puis nous avons stocké les panneaux en tant que modèles de visualisation (camembert, barre, métrique) dans lesquels nous pouvons remplacer certains paramètres pour créer de nouvelles visualisations avec différents champs en fonction de la question.</p><p>Vous pouvez consulter les fichiers JSON du modèle <a href="https://github.com/Delacrobix/elasticsearch-labs/tree/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/templates"><strong>ici.</strong></a> Notez que nous avons modifié les valeurs de l&#x27;objet que nous voulons remplacer plus tard par {<code>variable_name</code>}<br/></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ec7e1ac0cd8b470df13e60940162b56778acb386-315x234.png" style="width: 100%;" /><p>Grâce aux informations fournies par le mécanisme d&#x27;apprentissage tout au long de la vie, nous pouvons décider du modèle à utiliser et des valeurs à remplacer.</p><p><code>fill_template_with_analysis</code> recevra les paramètres pour un seul panneau, y compris le modèle JSON de la visualisation, un titre, un champ et les coordonnées de la visualisation sur la grille.</p><p>Ensuite, il remplacera les valeurs du modèle et renverra la visualisation JSON finale.</p><pre><code>def fill_template_with_analysis(
    template: Dict[str, Any],
    visualization: Visualization,
    grid_data: Dict[str, Any],
):
    template_str = json.dumps(template)
    replacements = {
	 "{visualization_id}": str(uuid.uuid4()),
        "{title}": visualization.title,
        "{x}": grid_data["x"],
        "{y}": grid_data["y"],
    }

    if visualization.field:
        replacements["{field}"] = visualization.field

    for placeholder, value in replacements.items():
        template_str = template_str.replace(placeholder, str(value))

    return json.loads(template_str)</code></pre><p>Pour faire simple, nous aurons des coordonnées statiques que nous assignerons aux panneaux que le LLM décidera de créer et nous produirons un tableau de bord à grille 2x2 comme l&#x27;image ci-dessus.</p><pre><code># Filling templates fields
panels = []    
grid_data = [
    {"x": 0, "y": 0},
    {"x": 12, "y": 0},
    {"x": 0, "y": 12},
    {"x": 12, "y": 12},
]


i = 0

for vis in dashboard_values.visualizations:
    for vis_type in vis.type:
        template = templates.get(vis_type, templates.get("bar", {}))
        filled_panel = fill_template_with_analysis(template, vis, grid_data[i])
        panels.append(filled_panel)
        i += 1</code></pre><p>En fonction du type de visualisation décidé par le LLM, nous choisirons un modèle de fichier JSON et remplacerons les informations pertinentes à l&#x27;aide de <code>fill_template_with_analysis</code> , puis nous ajouterons le nouveau panneau à un tableau que nous utiliserons ultérieurement pour créer le tableau de bord.</p><p>Lorsque le tableau de bord est prêt, nous utilisons l&#x27;<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-dashboards-dashboard-id">API Create a dashboard</a> pour envoyer le nouveau fichier JSON à Kibana afin de générer le tableau de bord :<br/></p><pre><code>try:
    dashboard_id = str(uuid.uuid4())

    # post request to create the dashboard endpoint
    url = f"{os.getenv('KIBANA_URL')}/api/dashboards/dashboard/{dashboard_id}"

    dashboard_config = {
        "attributes": {
            "title": dashboard_values.title,
            "description": "Generated by AI",
            "timeRestore": True,
            "panels": panels,  # Visualizations with the values generated by the LLM
            "timeFrom": "now-7d/d",
            "timeTo": "now",
        },
    }

    headers = {
        "Content-Type": "application/json",
        "kbn-xsrf": "true",
        "Authorization": f"ApiKey {os.getenv('ELASTICSEARCH_API_KEY')}",
    }

    requests.post(
        url,
        headers=headers,
        json=dashboard_config,
    )

    # Url to the generated dashboard
    dashboard_url = f"{os.getenv('KIBANA_URL')}/app/dashboards#/view/{dashboard_id}"

    print("Dashboard URL: ", dashboard_url)
    print("Dashboard ID: ", dashboard_id)

except Exception as e:
    print(f"Failed to create dashboard: {str(e)}")</code></pre><p>Pour exécuter le script et générer le tableau de bord, exécutez la commande suivante dans la console :</p><pre><code>python <file_name>.py</code></pre><p>Le résultat final sera le suivant :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e909afbf0e47d9a6e0f7bd07dfb2efcfa5cf06ac-921x715.png" style="width: 100%;" /><h2>Conclusion</h2><p>Les LLM démontrent leurs fortes capacités visuelles lorsqu&#x27;ils transforment du texte en code ou des images en code. L&#x27;API des tableaux de bord permet également de transformer des fichiers JSON en tableaux de bord, et avec un LLM et un peu de code, nous pouvons transformer des images en tableau de bord Kibana.</p><p>L&#x27;étape suivante consiste à améliorer la flexibilité des visuels des tableaux de bord en utilisant différents paramètres de grille, différentes tailles de tableau de bord et différentes positions. De plus, la prise en charge de visualisations et de types de visualisation plus complexes serait un ajout utile à cette application.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dévoiler des modèles uniques : Un guide pour l'agrégation de termes significatifs dans Elasticsearch]]></title>
            <description><![CDATA[Apprenez à utiliser l'agrégation de termes significatifs pour découvrir des informations dans vos données.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/significant-terms-aggregation-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/significant-terms-aggregation-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
            <pubDate>Mon, 07 Jul 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/13adbc901837835bb56abf15e377127b017cfac8-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans Elasticsearch, une <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation">agrégation de termes significatifs</a> va au-delà des <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-terms-aggregation">termes les plus courants</a> pour trouver des valeurs statistiquement inhabituelles dans un ensemble de données. Cela nous permet de découvrir des informations précieuses et des modèles non évidents. Une agrégation de termes significatifs fournit une réponse avec deux paramètres utiles :</p><ul><li><strong>bg_count (background count) : </strong>Nombre de documents trouvés dans l&#x27;ensemble de données parent.</li><li><strong>doc_count :</strong> Nombre de documents trouvés dans l&#x27;ensemble de données de résultat</li></ul><p>Par exemple, dans un ensemble de données sur les ventes de téléphones, nous pouvons rechercher des termes significatifs sur les ventes de l&#x27;iPhone 16 comme suit :</p><pre><code>GET phone_sales_analysis/_search
{
 "size": 0,
 "query": {
   "term": {
     "phone_model": {
       "value": "iPhone 16"
     }
   }
 },
 "aggs": {
   "significant_cities": {
     "significant_terms": {
       "field": "city_region",
       "size": 1
     }
   }
 }
}</code></pre><p>Ensuite, la réponse nous donne :</p><pre><code>{
 "aggregations": {
   "significant_cities": {
     "doc_count": 122,
     "bg_count": 424,
     "buckets": [
       {
         "key": "Houston",
         "doc_count": 12,
         "score": 0.1946481360617346,
         "bg_count": 14
       }

     ]
   }
 }
}</code></pre><p>Houston ne figure pas parmi les 10 premières villes de l&#x27;ensemble des données, ni parmi les premières villes pour l&#x27;iPhone 16. Toutefois, l&#x27;agrégation des termes significatifs a montré que l&#x27;<strong><em> iPhone 16 est acheté de manière disproportionnée dans cette ville</em></strong> par rapport au reste des données. Voyons plus en détail les chiffres :</p><ul><li><strong>Au niveau supérieur :</strong><ul><li><strong>doc_count : 122 - </strong>La requête correspond à 122 documents au total</li><li><strong>bg_count : </strong>424 - Le jeu d&#x27;arrière-plan (tous les documents de vente) contient 424 documents.</li></ul></li><li><strong>Dans le seau de Houston :</strong><ul><li><strong>doc_count : 12 - </strong>Houston apparaît dans 12 des 122 résultats de la requête</li><li><strong>bg_count : 14 - </strong>Houston apparaît dans 14 des 424 documents de l&#x27;ensemble de données de base.</li></ul></li></ul><p>Cela nous indique que sur 424 achats totaux, seuls 14 ont eu lieu à Houston, soit 3,3% de tous les achats. Toutefois, si nous ne prenons en compte que les ventes d&#x27;iPhone 16, nous constatons que 12 des 122 ventes ont eu lieu à Houston, soit 9,8%, 3 fois plus que dans l&#x27;ensemble de l&#x27;ensemble des données ; c&#x27;est significatif !</p><p>Voici ce que cela donne en termes de visualisation : Ventes totales par ville_région.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f31335b0b7793650025f941820f238dd35bfb09f-1486x1066.png" style="width: 100%;" /><p>Nous pouvons voir qu&#x27;il y a 14 ventes à Houston, ce qui en fait la 14e ville en termes de ventes dans l&#x27;ensemble des données.</p><p>Maintenant, si nous appliquons un filtre pour ne regarder que les ventes d&#x27;iPhone 16, nous avons 12 ventes à Houston, ce qui en fait la deuxième ville avec le plus grand nombre de ventes pour ce modèle spécifique :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6516040db77e6c62af5541a74c723b18008ad3c6-1472x1038.png" style="width: 100%;" /><h2>Comprendre l&#x27;agrégation des termes significatifs</h2><p>Selon la documentation d&#x27;Elastic, les <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation">termes significatifs sont agrégation</a>:</p><p><em>&quot;(Trouve) des termes qui ont subi un changement significatif de popularité mesuré entre un ensemble de premier plan et un ensemble d&#x27;arrière-plan.&quot;</em></p><p>Cela signifie qu&#x27;il utilise des mesures statistiques pour comparer la fréquence d&#x27;un terme dans un sous-ensemble de données (l&#x27;ensemble de premier plan) à la fréquence du même terme dans l&#x27;ensemble de données parent (l&#x27;ensemble d&#x27;arrière-plan). De cette manière, la notation reflète la signification statistique plutôt que la fréquence d&#x27;apparition d&#x27;un terme dans les données.</p><p>Les principales différences entre une agrégation de termes significatifs et une agrégation de termes normaux sont les suivantes :</p><ul><li>Les termes significatifs comparent un sous-ensemble de données, tandis qu&#x27;une agrégation de termes ne fonctionne que sur l&#x27;ensemble de données résultant de la requête.</li><li>Les résultats d&#x27;une agrégation de termes sont les termes les plus courants de l&#x27;ensemble de données, tandis que les résultats d&#x27;une agrégation de termes significatifs ignorent les termes courants pour trouver ce qui rend l&#x27;ensemble de données unique.</li><li>Les termes significatifs peuvent avoir un impact plus important sur les performances, étant donné qu&#x27;ils doivent extraire des données du disque plutôt que de la mémoire, comme le fait l&#x27;agrégation des termes.</li></ul><h2>Application pratique (analyse du comportement du consommateur)</h2><h3>Préparation des données pour l&#x27;analyse</h3><p>Pour cette analyse, nous avons généré un ensemble de données synthétiques sur les ventes de téléphones, comprenant le prix, les caractéristiques du téléphone, les données démographiques de l&#x27;acheteur et les commentaires. Nous avons également généré des embeddings à partir des commentaires de l&#x27;utilisateur afin de pouvoir lancer une requête sémantique ultérieurement. Nous avons utilisé le <a href="https://huggingface.co/intfloat/multilingual-e5-small">modèle multilingue e5 small</a>, disponible prêt à l&#x27;emploi sur Elasticsearch.</p><p></p><p>Pour utiliser ce jeu de données sur Elasticsearch :</p><ol><li>Téléchargez le fichier CSV (téléchargeable <a href="https://github.com/Alex1795/significant_terms_blog_dataset/blob/main/phone_sales_analysis_dataset.csv">ici)</a> à l&#x27;aide de la fonction Kibana <a href="https://www.elastic.co/docs/manage-data/ingest/upload-data-files">Upload data files.</a></li><li>Créez un champ sémantique, comme indiqué dans <a href="https://www.elastic.co/search-labs/blog/chat-with-pdf-elastic-playground#upload-pdfs-to-kibana">ce blog</a>, appelé &quot;embedding&quot;, qui utilise le champ sémantique <code>multilingual-e5-small model</code></li><li>Terminez l&#x27;importation en conservant le type de champ par défaut (mot-clé pour chaque champ à l&#x27;exception de <code>purchase_date</code> et <code>user_feedback)</code>). Veillez à ajouter le nom de l&#x27;index <code>phone_sales_analysis</code> pour pouvoir exécuter les requêtes présentées ici telles quelles.</li></ol><p>L&#x27;objectif principal de cette analyse est de découvrir <strong><em>&quot;ce qui différencie les acheteurs de l&#x27;iPhone 16 des autres segments de la population</em></strong>et d&#x27;obtenir une segmentation des acheteurs à des fins de marketing. </p><p>Il s&#x27;agit d&#x27;un exemple de document provenant de l&#x27;ensemble de données :</p><pre><code>{
         "customer_type": "Returning",
         "user_feedback": "I have to say, quality is great for the price. The battery life is really good.",
         "upgrade_frequency": "2 years",
         "storage_capacity": "256GB",
         "occupation": "Technology & Data",
         "color": "Phantom Black",
         "gender": "Male",
         "price_paid": 899,
         "previous_brand_loyalty": "Mixed",
         "location_type": "Urban",
         "phone_model": "Samsung Galaxy S24",
         "city_region": "San Francisco Bay Area",
         "@timestamp": "2024-03-15T00:00:00.000-05:00",
         "income_bracket": "75000-100000",
         "purchase_channel": "Online",
         "feedback_sentiment": "positive",
         "education_level": "Bachelor",
         "embedding": "I have to say, quality is great for the price. The battery life is really good.",
         "customer_id": "C001",
         "purchase_date": "2024-03-15",
         "age": 34,
         "trade_in_model": "iPhone 13"
}</code></pre><h3>Comprendre les modèles démographiques</h3><p>Ici, nous allons effectuer une analyse sur la population générale et la comparer aux résultats intéressants des agrégations de termes significatifs pour les utilisateurs de l&#x27;iPhone 16.</p><h4>Modèles normaux</h4><p>Pour comprendre les habitudes d&#x27;achat, nous pouvons regrouper les données de tous les documents dans différents domaines. Pour simplifier, nous nous concentrerons sur l&#x27;étude des professions des personnes qui ont acheté un téléphone. Nous pouvons le faire en envoyant une requête à Elasticsearch.</p><pre><code>GET phone_sales_analysis/_search
{
 "aggs": {
   "occupation_distribution": {
     "terms": {
       "size": 5,
       "field": "occupation"
     }
   }
 },
 "size": 0
}</code></pre><p>Cela nous indique que les principales professions de l&#x27;ensemble de données (par nombre d&#x27;enregistrements) sont les suivantes :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99136ddddd7abad5d74481158a04501b6915441b-1518x480.png" style="width: 100%;" /><h4>Les habitudes des utilisateurs de l&#x27;iPhone 16</h4><p>Pour comprendre ce qui différencie les personnes qui ont acheté un iPhone 16, exécutons une agrégation de termes sur le même champ avec un filtre pour trouver ces personnes dans la requête, comme ceci :</p><pre><code>GET phone_sales_analysis/_search
{
  "query": {
    "term": {
      "phone_model": "iPhone 16"
    }
  },
  "aggs": {
    "occupation_distribution": {
      "terms": {
        "size": 5,
        "field": "occupation"
      }
    }
  },
  "size": 0
}</code></pre><p>Ainsi, pour les utilisateurs de l&#x27;iPhone 16, les principales professions sont les suivantes :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/36ce86475beb03810c6ad81d7c776d1eec736654-1500x484.png" style="width: 100%;" /><p>Nous pouvons constater que les utilisateurs de l&#x27;iPhone 16 ont des habitudes d&#x27;occupation différentes de celles des utilisateurs d&#x27;autres modèles de téléphone. Utilisons Kibana pour visualiser facilement les résultats :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b70ab05bc962a274e1617b6caf20575c489a62d8-1448x1128.png" style="width: 100%;" /><p></p><p>Dans ce graphique, nous pouvons voir que la tendance pour l&#x27;iPhone 16 est différente de la tendance de la population entière.</p><p>Nous pouvons sauter toute cette analyse pour voir ce qui différencie les utilisateurs de l&#x27;iPhone 16 de la population générale en effectuant une agrégation de termes significatifs :</p><pre><code>GET phone_sales_analysis/_search
{
  "query": {
    "term": {
      "phone_model": "iPhone 16"
    }
  },
  "aggs": {
    "occupation_distribution": {
      "significant_terms": {
        "size": 5,
        "field": "occupation"
      }
    }
  },
  "size": 0
}</code></pre><p>En résumé, nous obtenons cette réponse :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Valeurs des professions pour l'iPhone 16</td><td style="border: 1px solid #ccc; padding: 4px;">doc_count</td><td style="border: 1px solid #ccc; padding: 4px;">bg_count</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">occupation_distribution (niveau supérieur)</td><td style="border: 1px solid #ccc; padding: 4px;">122</td><td style="border: 1px solid #ccc; padding: 4px;">424</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Médical & Seau pour les soins de santé</td><td style="border: 1px solid #ccc; padding: 4px;">45</td><td style="border: 1px solid #ccc; padding: 4px;">57</td>
                    </tr>
              </tbody>
            </table>
          <p>La réponse suggère clairement que les utilisateurs de l&#x27;iPhone 16 ont un problème (significatif !) peu commun. le nombre de personnes travaillant dans le domaine médical &amp; par rapport à la population générale. Voyons ce que signifient les chiffres de la réponse :</p><ul><li><strong>Au niveau supérieur :</strong><ul><li><strong>doc_count : 122 - </strong>La requête correspond à 122 documents au total</li><li><strong>bg_count : </strong>424 - Le jeu d&#x27;arrière-plan (tous les documents de vente) contient 424 documents.</li></ul></li><li><strong>Dans le secteur médical &amp; Soins de santé :</strong><ul><li><strong>doc_count : 45 - </strong>&quot;Medical &amp; Healthcare&quot; apparaît dans 45 des 122 résultats de la requête</li><li><strong>bg_count : 57 - </strong>&quot;Medical &amp; Healthcare&quot; apparaît dans 57 des 424 documents totaux de l&#x27;ensemble de données d&#x27;arrière-plan.</li></ul></li></ul><p>Sur les 424 acheteurs, 57 travaillent dans le domaine médical &amp; - soit 13,44%. Mais si l&#x27;on considère les acheteurs de l&#x27;iPhone 16, 45 sur 122 travaillent dans le domaine médical &amp; - soit 36,88%. Cela signifie que nous avons deux fois plus de chances de trouver une personne travaillant dans le domaine médical &amp; healthcare parmi les utilisateurs de l&#x27;iPhone 16 !</p><p>Nous pouvons appliquer cette même analyse à d&#x27;autres champs (âge, localisation, tranche de revenus, etc.) pour obtenir plus d&#x27;informations sur ce qui rend les utilisateurs de l&#x27;iPhone 16 uniques. </p><h3>Segmentation des consommateurs</h3><p>Nous pouvons utiliser l&#x27;agrégation des termes significatifs pour extraire des informations sur les relations entre les produits, les catégories et les segments de clientèle. Pour ce faire, nous construisons une agrégation parente pour la catégorie que nous souhaitons explorer. Nous utilisons également une sous-agrégation des termes significatifs et des termes normaux pour trouver des informations intéressantes sur cette catégorie et les comparer à ce qu&#x27;utilisent la plupart des personnes dans cette profession.</p><p>Voyons, par exemple, ce que préfèrent les gens dans certains domaines d&#x27;activité :</p><ol><li>Pour rendre l&#x27;analyse plus claire, limitons notre recherche à trois domaines de travail : [&quot;Administratif &amp; Support&quot;, &quot;Technologie &amp; Données&quot;, &quot;Médical &amp; Soins de santé&quot;]</li><li>En ce qui concerne les agrégations, nous commençons par une agrégation des termes par profession</li><li>Ajoutez une sous-agrégation : termes par modèle de téléphone - pour savoir quels modèles les utilisateurs travaillant dans chaque domaine achètent.</li><li>Ajoutez une deuxième sous-agrégation : les termes significatifs par modèle de téléphone - pour trouver les modèles spéciaux dans chaque domaine de travail.</li></ol><pre><code>GET phone_sales_analysis/_search
{
 "query": {
   "terms": {
     "occupation": [
       "Administrative & Support",
       "Technology & Data",
       "Medical & Healthcare"
     ]
   }
 },
 "aggs": {
   "occupations": {
     "terms": {
       "size": 15,
       "field": "occupation"
     },
     "aggs": {
       "general_models": {
         "terms": {
           "field": "phone_model"
         }
       },
       "significant_models": {
         "significant_terms": {
           "field": "phone_model"
         }
       }
     }
   }
 },
 "size": 0
}</code></pre><p>Décortiquons les résultats de l&#x27;agrégation :</p><p><strong>Profession</strong>: Soutien administratif &amp;</p><p><strong>Agrégation de termes</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4ad519c9013867a3f4cee032160eadd8a47804a-1506x398.png" style="width: 100%;" /><p><strong>Agrégation des termes significatifs</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e5604fa8036667bdfe733576a5e7c6153760dd3a-306x220.png" style="width: 100%;" /><p>Ce tableau permet de déduire qu&#x27;il n&#x27;y a pas de différences significatives entre l&#x27;évolution de cette profession et celle de l&#x27;ensemble de la population.</p><p><strong>Profession</strong>: Technologie &amp; Données</p><p><strong>Agrégation de termes</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/13b09039bb7d183276451007d2d69dc190b1d3c0-1508x836.png" style="width: 100%;" /><p></p><p><strong>Agrégation des termes significatifs</strong></p><p>Total des documents : 424</p><p>Documents dans cette profession : 71</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">modèle de téléphone</td><td style="border: 1px solid #ccc; padding: 4px;">doc_count (ce modèle dans cette profession)</td><td style="border: 1px solid #ccc; padding: 4px;">bg_count (ce modèle figure dans tous les documents)</td><td style="border: 1px solid #ccc; padding: 4px;">% dans tous les documents</td><td style="border: 1px solid #ccc; padding: 4px;">% dans cette profession</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Google Pixel 8</td><td style="border: 1px solid #ccc; padding: 4px;">12</td><td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">5.19%</td><td style="border: 1px solid #ccc; padding: 4px;">16.90%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OnePlus 11</td><td style="border: 1px solid #ccc; padding: 4px;">9</td><td style="border: 1px solid #ccc; padding: 4px;">14</td><td style="border: 1px solid #ccc; padding: 4px;">3,30 %</td><td style="border: 1px solid #ccc; padding: 4px;">12.68%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OnePlus 12 Pro</td><td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">0,71 %</td><td style="border: 1px solid #ccc; padding: 4px;">4.23%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Google Pixel 8 Pro</td><td style="border: 1px solid #ccc; padding: 4px;">9</td><td style="border: 1px solid #ccc; padding: 4px;">21</td><td style="border: 1px solid #ccc; padding: 4px;">4.95%</td><td style="border: 1px solid #ccc; padding: 4px;">12.68%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Rien Téléphone 2</td><td style="border: 1px solid #ccc; padding: 4px;">5</td><td style="border: 1px solid #ccc; padding: 4px;">8</td><td style="border: 1px solid #ccc; padding: 4px;">1.89%</td><td style="border: 1px solid #ccc; padding: 4px;">7.04%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Samsung Galaxy Z Fold5</td><td style="border: 1px solid #ccc; padding: 4px;">4</td><td style="border: 1px solid #ccc; padding: 4px;">6</td><td style="border: 1px solid #ccc; padding: 4px;">1.42%</td><td style="border: 1px solid #ccc; padding: 4px;">5.63%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OnePlus 12</td><td style="border: 1px solid #ccc; padding: 4px;">8</td><td style="border: 1px solid #ccc; padding: 4px;">20</td><td style="border: 1px solid #ccc; padding: 4px;">4.72%</td><td style="border: 1px solid #ccc; padding: 4px;">11,27 %</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>Profession</strong>: Médical : &amp; Soins de santé</p><p><strong>Agrégation de termes</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b008e996742fc0bb48dc6bacff17cfbc56cf0d73-1492x398.png" style="width: 100%;" /><p><strong>Agrégation des termes significatifs</strong></p><p>Total des documents : 424</p><p>Documents dans cette profession : 57</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">modèle de téléphone</td><td style="border: 1px solid #ccc; padding: 4px;">doc_count (ce modèle dans cette profession)</td><td style="border: 1px solid #ccc; padding: 4px;">bg_count (ce modèle figure dans tous les documents)</td><td style="border: 1px solid #ccc; padding: 4px;">% dans tous les documents</td><td style="border: 1px solid #ccc; padding: 4px;">% dans cette profession</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 16</td><td style="border: 1px solid #ccc; padding: 4px;">45</td><td style="border: 1px solid #ccc; padding: 4px;">122</td><td style="border: 1px solid #ccc; padding: 4px;">28.77%</td><td style="border: 1px solid #ccc; padding: 4px;">78.95%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 15 Pro Max</td><td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">13</td><td style="border: 1px solid #ccc; padding: 4px;">3,07 %</td><td style="border: 1px solid #ccc; padding: 4px;">5.26%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 15</td><td style="border: 1px solid #ccc; padding: 4px;">7</td><td style="border: 1px solid #ccc; padding: 4px;">40</td><td style="border: 1px solid #ccc; padding: 4px;">9.43%</td><td style="border: 1px solid #ccc; padding: 4px;">12.28%</td>
                    </tr>
              </tbody>
            </table>
          <p>Voyons ce que ces données nous apprennent :</p><ul><li>Medical &amp; Les professionnels de la santé préfèrent l&#x27;iPhone 16 et sont très enclins à utiliser les téléphones Apple en général.</li><li>Technologie &amp; Les professionnels des données préfèrent les téléphones Android haut de gamme, mais n&#x27;utilisent pas nécessairement la marque Samsung. Dans cette catégorie, les iPhones ont également le vent en poupe.</li><li>Les professionnels de l&#x27;assistance administrative &amp; préfèrent les téléphones Samsung et Google, mais n&#x27;ont pas de tendance forte et unique.</li></ul><h3>Agrégation de termes significatifs et recherche hybride</h3><p>La recherche hybride combine la recherche textuelle et les résultats sémantiques pour offrir une meilleure expérience de recherche. Dans ce contexte, une agrégation significative de termes peut fournir des indications sur les résultats d&#x27;une recherche contextuelle en répondant à la question suivante : &quot;Qu&#x27;est-ce que cet ensemble de données a de particulier par rapport à l&#x27;ensemble des documents ? <strong>Qu&#x27;est-ce que cet ensemble de données a de particulier par rapport à tous les documents ?</strong>Pour illustrer cette fonctionnalité, voyons quels modèles sont surreprésentés lorsque les utilisateurs parlent de bonnes performances : </p><ul><li>Construisons une requête sémantique dans laquelle nous trouvons les meilleurs commentaires d&#x27;utilisateurs les plus proches de l&#x27;entrée &quot;bonne performance&quot; par rapport à l&#x27;intégration des champs.</li><li>Nous utiliserons également une recherche textuelle avec les mêmes termes sur le champ de texte user_feedback</li><li>Nous ajouterons également une requête de termes significatifs pour trouver des modèles de téléphone qui peuvent être trouvés plus fréquemment parmi ces résultats que dans l&#x27;ensemble des données.<br/></li></ul><pre><code>GET phone_sales_analysis/_search
{
 "retriever": {
   "rrf": {
     "retrievers": [
       {
         "standard": {
           "query": {
             "bool": {
               "must": [
                 {
                   "match": {
                     "user_feedback": {
                       "query": "good performance",
                       "operator": "and"
                     }
                   }
                 }
               ]
             }
           }
         }
       },
       {
         "standard": {
           "query": {
             "semantic": {
               "field": "embedding",
               "query": "good performance"
             }
           }
         }
       }
     ],
    "rank_window_size": 20
   }
 },
 "aggs": {
   "Models": {
     "significant_terms": {
       "field": "phone_model"
     }
   }
 }
}</code></pre><p>Examinons un exemple de documents correspondants :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4cb488097a382f0c28c21540db4f593d23633473-1600x162.png" style="width: 100%;" /><p>Voici la réponse que nous recevons :</p><pre><code>{
  "took": 388,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 20,
      "relation": "eq"
    },
    "max_score": 0.016393442,
    "hits": [...]
  },
  "aggregations": {
    "Models": {
      "doc_count": 20,
      "bg_count": 424,
      "buckets": [
        {
          "key": "iPhone 15",
          "doc_count": 5,
          "score": 0.4125,
          "bg_count": 40
        }
      ]
    }
  }
}</code></pre><p></p><p>Cela nous indique que si un iPhone 15 est rencontré 40 fois sur un total de 424 documents (9,4% des documents), il peut être trouvé 5 fois dans les 20 documents qui correspondent à la recherche sémantique &quot;bonne performance&quot; (25% des documents). Nous pouvons donc tirer une conclusion : un iPhone 15 a 2,7 fois plus de chances d&#x27;être trouvé lorsqu&#x27;on parle de bonnes performances que par hasard.</p><h2>Conclusion</h2><p>L&#x27;agrégation des termes significatifs permet de découvrir des détails uniques d&#x27;un ensemble de données en le comparant à l&#x27;univers des documents. Cela peut révéler des relations inattendues dans nos données, allant au-delà du nombre d&#x27;occurrences. Nous pouvons appliquer des termes significatifs dans divers cas d&#x27;utilisation qui permettent des fonctionnalités très intéressantes, par exemple :</p><ul><li><a href="https://www.elastic.co/blog/significant-terms-aggregation#credit">Trouvez des modèles lorsque vous travaillez sur la détection </a>des fraudes - identifiez les transactions courantes pour les cartes de crédit volées.</li><li>Informations sur la qualité de la marque à partir des avis des utilisateurs - détectez une marque dont le nombre d&#x27;avis négatifs est disproportionné.</li><li><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation#_use_on_free_text_fields">Repérer </a>les documents mal classés - repérer les documents qui appartiennent à une catégorie (filtre de termes) et qui utilisent des mots peu courants de la catégorie dans une description (agrégation de termes significatifs).</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construire un assistant agentique RAG avec JavaScript, Mastra et Elasticsearch]]></title>
            <description><![CDATA[Apprendre à construire des agents d'intelligence artificielle dans l'écosystème JavaScript]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/agentic-rag</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/agentic-rag</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8132ed781c1ea5d46ca244182f421ed5c721f23b-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Cette idée m&#x27;est venue alors que je me trouvais au beau milieu d&#x27;une ligue de basket-ball fantastique passionnante et aux enjeux considérables. Je me suis posé la question : <em>Pourrais-je construire un agent IA qui m&#x27;aiderait à dominer mes matchs hebdomadaires ? Absolument !</em></p><p>Dans ce billet, nous allons voir comment construire un assistant RAG agentique en utilisant <a href="https://mastra.ai/en/docs">Mastra</a> et une application web JavaScript légère pour interagir avec lui. En connectant cet agent à Elasticsearch, nous lui donnons accès aux données structurées des joueurs et la possibilité d&#x27;exécuter des agrégations statistiques en temps réel, afin de vous donner des recommandations fondées sur les statistiques des joueurs. Rendez-vous sur le <a href="https://github.com/jdarmada/nba-ai-assistant-js.git">repo</a> GitHub pour suivre le processus ; le <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/README.md">README</a> fournit des instructions sur la manière de cloner et d&#x27;exécuter l&#x27;application par vos propres moyens. </p><p>Voici à quoi il devrait ressembler une fois assemblé :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c73bd1dc1b5fe54f025c7a2b7c322acc9122f3a-1999x1393.png" style="width: 100%;" /><p>Remarque : cet article de blog s&#x27;appuie sur &quot;<a href="https://www.elastic.co/search-labs/blog/ai-agents-ai-sdk-elasticsearch">Building AI Agents with AI SDK and Elastic&quot;</a> ( Créer des agents d&#x27;intelligence artificielle avec AI SDK et Elastic). Si vous ne connaissez pas encore les agents d&#x27;intelligence artificielle en général et leur utilité, commencez par là.<br/></p><h2><strong>Aperçu de l&#x27;architecture</strong></h2><p>Au cœur du système se trouve un grand modèle de langage (LLM), qui agit comme le moteur de raisonnement de l&#x27;agent (le cerveau). Il interprète les données de l&#x27;utilisateur, décide des outils à appeler et orchestre les étapes nécessaires pour générer une réponse pertinente.</p><p>L&#x27;agent lui-même est soutenu par Mastra, un cadre d&#x27;agent dans l&#x27;écosystème JavaScript. Mastra intègre le LLM à une infrastructure dorsale, l&#x27;expose en tant que point d&#x27;extrémité de l&#x27;API et fournit une interface pour définir les outils, les invites du système et le comportement de l&#x27;agent.</p><p>Sur le frontend, nous utilisons <a href="https://vite.dev/guide/">Vite</a> pour mettre en place rapidement une application web React qui fournit une interface de chat pour envoyer des requêtes à l&#x27;agent et recevoir ses réponses.</p><p>Enfin, nous avons Elasticsearch, qui stocke les statistiques des joueurs et les données de correspondance que l&#x27;agent peut interroger et agréger.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/443bdc00d84ed1dd49e9f9e431e86ca4b0892563-1999x977.png" style="width: 100%;" /><h2><strong>Arrière-plan</strong></h2><p>Passons en revue quelques concepts fondamentaux :</p><h3><strong>Qu&#x27;est-ce que le RAG agentique ?</strong></h3><p>Les agents d&#x27;intelligence artificielle peuvent interagir avec d&#x27;autres systèmes, fonctionner de manière indépendante et effectuer des actions en fonction de paramètres définis. Le RAG agentique combine l&#x27;autonomie d&#x27;un agent d&#x27;intelligence artificielle avec les principes de la génération augmentée par récupération, ce qui permet à un LLM de choisir les outils à utiliser et les données à utiliser comme contexte pour générer une réponse. Pour en savoir plus sur le RAG <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">, cliquez ici.</a></p><h3><strong>Pourquoi aller plus loin que AI-SDK dans le choix d&#x27;un framework ?</strong></h3><p>Il existe de nombreuses structures d&#x27;agents d&#x27;IA et vous avez probablement entendu parler des plus populaires comme <a href="https://www.elastic.co/search-labs/blog/using-crewai-with-elasticsearch">CrewAI</a>, <a href="https://www.elastic.co/search-labs/blog/using-autogen-with-elasticsearch">AutoGen</a> et <a href="https://www.elastic.co/search-labs/blog/build-rag-workflow-langgraph-elasticsearch">LangGraph</a>. La plupart de ces cadres partagent un ensemble commun de fonctionnalités, notamment la prise en charge de différents modèles, l&#x27;utilisation d&#x27;outils et la gestion de la mémoire.</p><p>Voici une <a href="https://docs.google.com/spreadsheets/d/1B37VxTBuGLeTSPVWtz7UMsCdtXrqV5hCjWkbHN8tfAo/edit?gid=0#gid=0">fiche comparative de</a> Harrison Chase (PDG de LangChain).</p><p>Ce qui a suscité mon intérêt pour Mastra, c&#x27;est qu&#x27;il s&#x27;agit d&#x27;un framework JavaScript conçu pour les développeurs full-stack afin d&#x27;intégrer facilement des agents dans leur écosystème. L&#x27;AI-SDK de Vercel permet également de réaliser la plupart de ces tâches, mais c&#x27;est lorsque vos projets incluent des flux de travail d&#x27;agents plus complexes que Mastra brille. Mastra améliore les modèles de base définis par l&#x27;AI-SDK et, dans ce projet, nous les utiliserons en tandem.</p><h3><strong>Cadres et considérations sur le choix du modèle</strong></h3><p>Si ces frameworks peuvent vous aider à créer rapidement des agents d&#x27;intelligence artificielle, ils présentent néanmoins certains inconvénients. Par exemple, l&#x27;utilisation d&#x27;autres cadres en dehors des agents d&#x27;IA ou de toute couche d&#x27;abstraction en général vous fait perdre un peu de contrôle. Si le LLM n&#x27;utilise pas les outils correctement ou fait quelque chose que vous ne voulez pas qu&#x27;il fasse, l&#x27;abstraction rend le débogage plus difficile. Cependant, à mon avis, ce compromis vaut la facilité et la rapidité que vous obtenez lors de la construction, en particulier parce que ces cadres gagnent du terrain et font l&#x27;objet d&#x27;itérations constantes.</p><p>Encore une fois, ces cadres sont agnostiques, ce qui signifie que vous pouvez brancher et utiliser différents modèles. N&#x27;oubliez pas que les modèles varient en fonction des ensembles de données sur lesquels ils ont été formés et qu&#x27;à leur tour, ils varient en fonction des réponses qu&#x27;ils donnent. Certains modèles ne prennent même pas en charge l&#x27;appel d&#x27;outils. Il est donc possible de changer et de tester différents modèles pour voir lequel vous donne les meilleures réponses, mais gardez à l&#x27;esprit que vous devrez probablement réécrire l&#x27;invite du système pour chacun d&#x27;entre eux. Par exemple, en utilisant Llama3.3 par rapport au GPT-4o, implique beaucoup plus d&#x27;invites et d&#x27;instructions spécifiques pour obtenir la réponse souhaitée.</p><h3><strong>Basket-ball fantaisie NBA</strong></h3><p>Le basket-ball fantaisie consiste à créer une ligue avec un groupe d&#x27;amis (attention, selon le degré de compétition de votre groupe, cela peut affecter le statut de vos amitiés), généralement avec de l&#x27;argent en jeu. Chacun d&#x27;entre vous constitue ensuite une équipe de 10 joueurs pour affronter les 10 joueurs d&#x27;un autre ami, en alternance chaque semaine. Les points qui contribuent à votre score global sont les résultats obtenus par chacun de vos joueurs contre leurs adversaires au cours d&#x27;une semaine donnée.</p><p>Si un joueur de votre équipe se blesse, est suspendu, etc., il y a une liste d&#x27;agents libres disponibles pour compléter votre équipe. C&#x27;est là qu&#x27;intervient une grande partie de la réflexion dans les sports fantastiques, car vous ne disposez que d&#x27;un nombre limité de choix et tout le monde est constamment à la recherche du meilleur joueur.</p><p>C&#x27;est là que notre assistant NBA AI va briller, en particulier dans les situations où vous devez rapidement décider quel joueur choisir. Au lieu de devoir rechercher manuellement les performances d&#x27;un joueur contre un adversaire spécifique, l&#x27;assistant peut trouver ces données rapidement et comparer les moyennes pour vous donner une recommandation éclairée.</p><p>Maintenant que vous connaissez les bases du RAG agentique et du basket-ball fantastique NBA, voyons ce qu&#x27;il en est dans la pratique.</p><h2><strong>Construire le projet</strong></h2><p>Si vous êtes bloqué à un moment ou à un autre ou si vous ne voulez pas le construire à partir de zéro, veuillez vous référer au <a href="https://github.com/jdarmada/nba-ai-assistant-js.git">repo</a>.</p><h3><strong>Ce que nous allons couvrir</strong></h3><ol><li><strong>L&#x27;échafaudage du projet :</strong><ol><li><strong>Backend (Mastra) :</strong> Utilisez npx create mastra@latest pour échafauder le backend et définir la logique de l&#x27;agent.</li><li><strong>Frontend (Vite + React) :</strong> Utilisez npm create vite@latest pour construire l&#x27;interface de chat frontale pour interagir avec l&#x27;agent.</li></ol></li><li><strong>Mise en place de variables d&#x27;environnement</strong><ol><li>Installer dotenv pour gérer les variables d&#x27;environnement.</li><li>Créer un fichier .env et fournir les variables nécessaires.</li></ol></li><li><strong>Configuration d&#x27;Elasticsearch</strong><ol><li>Mettre en place un cluster Elasticsearch (localement ou sur le cloud).</li><li>Installer le client Elasticsearch officiel.</li><li>S&#x27;assurer que les variables d&#x27;environnement sont accessibles.</li><li>Établir la connexion avec le client.</li></ol></li><li><strong>Acquisition en masse de données NBA dans Elasticsearch</strong><ol><li>Créez un index avec les mappings appropriés pour permettre les agrégations.</li><li>Intégrez en masse les statistiques de jeu des joueurs à partir d&#x27;un fichier CSV dans un index Elasticsearch.</li></ol></li><li><strong>Définir les agrégations Elasticsearch</strong><ol><li>Requête pour calculer les moyennes historiques contre un adversaire spécifique.</li><li>Requête pour calculer les moyennes de la saison contre un adversaire spécifique.</li></ol></li><li><strong>Fichier utilitaire de comparaison des joueurs</strong><ol><li>Consolidation des fonctions d&#x27;aide et des agrégations Elasticsearch.</li></ol></li><li><strong>Construction de l&#x27;agent</strong><ol><li>Ajouter la définition de l&#x27;agent et l&#x27;invite du système.</li><li>Installer les outils zod et define.</li><li>Ajout d&#x27;une configuration intermédiaire pour gérer CORS.</li></ol></li><li><strong>Intégration de l&#x27;interface utilisateur</strong><ol><li>Utilisation de la fonction useChat de l&#x27;AI-SDK pour interagir avec l&#x27;agent.</li><li>Créer l&#x27;interface utilisateur pour tenir des conversations correctement formatées.</li></ol></li><li><strong>Exécution de l&#x27;application</strong><ol><li>Démarrez le backend (serveur Mastra) et le frontend (application React).</li><li>Exemples de requêtes et d&#x27;utilisation.</li></ol></li><li><strong>Et maintenant ? Rendre l&#x27;agent plus intelligent</strong><ol><li>Ajout de capacités de recherche sémantique pour permettre des recommandations plus pertinentes.</li><li>Activer l&#x27;interrogation dynamique en déplaçant la logique de recherche vers le serveur Elasticsearch MCP (Model Context Protocol).</li></ol></li></ol><h3><strong>Produits requis</strong></h3><ul><li><strong>Node.js et npm</strong>: Le backend et le frontend fonctionnent tous deux sur Node. Assurez-vous d&#x27;avoir installé Node 18+ et npm v9+ (qui est fourni avec Node 18+).</li><li><strong>Cluster Elasticsearch :</strong> Un cluster Elasticsearch actif, soit localement, soit sur le cloud.</li><li><strong>Clé API OpenAI</strong>: Générez-en une sur la page des clés API du <a href="https://platform.openai.com/api-keys">portail des développeurs d&#x27;OpenAI</a>.</li></ul><p></p><h3><strong>Structure du projet</strong></h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c0bde11ad0eead523a95e03b9b905aa776e3fd1-1420x934.png" style="width: 100%;" /><h4><strong>Étape 1 : Échafaudage du projet</strong></h4><ol><li>Tout d&#x27;abord, créez le répertoire nba-ai-assistant-js et naviguez à l&#x27;intérieur en utilisant : </li></ol><pre><code>mkdir nba-ai-assistant-js && cd nba-ai-assistant-js</code></pre><p><strong>Backend :</strong></p><ol><li>Utilisez l&#x27;outil de création Mastra avec la commande : </li></ol><pre><code>npx create-mastra@latest</code></pre><p>2. Vous devriez obtenir quelques invites dans votre terminal, pour la première, nous nommerons le projet backend :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/de2725031ed6837db99a979efcdd0ece1e197dbb-608x84.png" style="width: 100%;" /><p>3. Ensuite, nous conserverons la structure par défaut pour le stockage des fichiers Mastra, en saisissant <code>src/</code>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/88919d9ff1852126395e1fcd700ecb1b59aac63c-866x116.png" style="width: 100%;" /><p>4. Ensuite, nous choisirons OpenAI comme fournisseur LLM par défaut.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2328761e769f3ded134e5a21e8a0bf8f41e88f68-404x210.png" style="width: 100%;" /><p>5. Enfin, il vous demandera votre clé API OpenAI. Pour l&#x27;instant, nous choisirons d&#x27;ignorer l&#x27;option et nous la fournirons plus tard dans un fichier<code> .env</code>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0662de9bd28758e377e4c63df8d08b479068ce63-444x120.png" style="width: 100%;" /><p><strong>Frontend :</strong></p><ol><li>Naviguez à nouveau vers le répertoire racine et exécutez l&#x27;<a href="https://vite.dev/guide/">outil de création Vite</a> à l&#x27;aide de cette commande : <code>npm create vite@latest frontend -- --template react</code></li></ol><p>Cela devrait créer une application React légère nommée <code>frontend</code> avec un modèle spécifique pour React.</p><p>Si tout se passe bien, à l&#x27;intérieur de votre répertoire de projet, vous devriez trouver un répertoire backend qui contient le code Mastra et un répertoire <code>frontend</code> avec votre application React.</p><p></p><h4><strong>Étape 2 : Configuration des variables d&#x27;environnement</strong></h4><ol><li>Pour gérer les clés sensibles, nous utiliserons le paquetage <code>dotenv</code> pour charger nos variables d&#x27;environnement à partir du fichier .env. fichier. Naviguez vers le répertoire backend et installez <code>dotenv</code>:</li></ol><pre><code>cd backend
npm install dotenv --save</code></pre><p>2. Dans le répertoire du backend, un fichier example.env est fourni avec les variables appropriées à remplir. Si vous créez le vôtre, veillez à inclure les variables suivantes :</p><pre><code># OpenAI Configuration
OPENAI_API_KEY=your_openai_api_key_here

# Elasticsearch Configuration
ELASTIC_ENDPOINT=your_elasticsearch_endpoint_here
ELASTIC_API_KEY=your_elasticsearch_api_key_here
</code></pre><p></p><p>Note : Assurez-vous que ce fichier est exclu de votre contrôle de version en ajoutant <code>.env</code> à <code>.gitignore</code>.</p><h4><strong>Étape 3 : Configuration d&#x27;Elasticsearch</strong></h4><p>Tout d&#x27;abord, vous devez disposer d&#x27;un cluster Elasticsearch actif. Deux options sont possibles :</p><ul><li><strong>Option A : utiliser Elasticsearch Cloud</strong><ul><li>S&#x27;inscrire à <a href="https://cloud.elastic.co/registration">Elastic Cloud</a></li><li>Créer un nouveau déploiement</li><li>Obtenez l&#x27;URL de votre point de terminaison et la clé API (encodée)</li></ul></li><li><strong>Option B : Exécuter Elasticsearch localement</strong><ul><li>Installer et exécuter Elasticsearch localement</li><li>Utilisez http://localhost:9200 comme point d&#x27;arrivée</li><li>Générer une clé API</li></ul></li></ul><p></p><p><strong>Installation du client Elasticsearch sur le backend :</strong></p><ol><li>Tout d&#x27;abord, installez le client Elasticsearch officiel dans votre répertoire backend :</li></ol><pre><code>npm install @elastic/elasticsearch</code></pre><p>2. Créez ensuite un répertoire lib pour contenir les fonctions réutilisables et naviguez-y :</p><pre><code>mkdir lib && cd lib</code></pre><p>3. À l&#x27;intérieur, créez un nouveau fichier appelé <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticClient.js">elasticClient.js</a>. Ce fichier initialise le client Elasticsearch et l&#x27;expose pour qu&#x27;il soit utilisé dans votre projet.</p><p>4. Comme nous utilisons des modules ECMAScript (ESM), le nom de fichier __dirname and __n&#x27;est pas disponible. Pour vous assurer que vos variables d&#x27;environnement sont correctement chargées à partir du fichier .env dans le dossier backend, ajoutez cette configuration au début de votre fichier :</p><pre><code>import { config } from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { Client } from '@elastic/elasticsearch';

// Grab current directory and load .env from backend folder
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envPath = join(__dirname, '../.env');

// Load environment variables from the correct path
config({ path: envPath });</code></pre><p>5. Maintenant, initialisez le client Elasticsearch en utilisant vos variables d&#x27;environnement et vérifiez la connexion :</p><pre><code>//Elastic client Initialization, make sure environment variables are being loaded in correctly
const config= {
    node: `${process.env.ELASTIC_ENDPOINT}`,
    auth: {
        apiKey: `${process.env.ELASTIC_API_KEY}`,
    },
};

export const elasticClient = new Client(config);

//Check if the client is connected
async function checkConnection() { 
    try {
        const info = await elasticClient.info();
        console.log('Elasticsearch is connected:', info);
    } catch (error) {
        console.error('Elasticsearch connection error:', error);
    }
}

checkConnection();
</code></pre><p>Maintenant, nous pouvons importer cette instance client dans n&#x27;importe quel fichier qui doit interagir avec votre cluster Elasticsearch.</p><p></p><h4><strong>Étape 4 : Intégration en masse des données NBA dans Elasticsearch</strong></h4><p><strong>Ensemble de données :</strong></p><p>Pour ce projet, nous ferons référence aux ensembles de données disponibles dans le répertoire <a href="https://github.com/jdarmada/nba-ai-assistant-js/tree/main/backend">backend/data</a> de la base de données. Notre assistant NBA utilisera ces données comme base de connaissances pour effectuer des comparaisons statistiques et générer des recommandations.</p><ul><li><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/sample_nba_data.csv">sample_player_game_stats.csv</a> - Exemple de statistiques de jeu d&#x27;un joueur (par exemple, points, rebonds, interceptions, etc.) par match et par joueur sur l&#x27;ensemble de sa carrière en NBA. Nous utiliserons cet ensemble de données pour effectuer des agrégations. (Remarque : il s&#x27;agit de données fictives, générées à des fins de démonstration et ne provenant pas de sources officielles de la NBA).</li><li><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/playerAndTeamInfo.js">playerAndTeamInfo.js</a> - Remplace les métadonnées sur les joueurs et les équipes qui seraient normalement fournies par un appel à l&#x27;API afin que l&#x27;agent puisse faire correspondre les noms des joueurs et des équipes aux identifiants. Comme nous utilisons des données d&#x27;échantillon, nous ne voulons pas nous encombrer d&#x27;une API externe, c&#x27;est pourquoi nous avons codé en dur certaines valeurs auxquelles l&#x27;agent peut se référer.</li></ul><p></p><p><strong>Mise en œuvre :</strong></p><ol><li>Dans le répertoire <code>backend/lib</code>, créez un fichier nommé <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/playerDataIngestion.js">playerDataIngestion.js</a>.</li><li>Configurer les importations, résoudre le chemin du fichier CSV et configurer l&#x27;analyse. Là encore, puisque nous utilisons ESM, nous devons reconstruire <code>__dirname</code> pour résoudre le chemin d&#x27;accès à l&#x27;échantillon CSV. Nous importerons également le module <a href="http://node.js/">Node.js</a> les modules intégrés, <code>fs</code> et <code>readline</code>, pour analyser le fichier CSV donné ligne par ligne.</li></ol><pre><code>import fs from 'fs';
import readline from 'readline';
import path from 'path';
import { fileURLToPath } from 'url';
import { elasticClient } from './elasticClient.js';

const indexName = 'sample-nba-player-data'; //Replace with your preferred index name

//Since we are using ES modules __dirname and __filename don't exist, so this is a workaround that allows us to use the absolute file path for our sample data.
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const filePath = path.resolve(__dirname, '../data/sample_nba_data.csv');</code></pre><p>Cela vous permet de lire et d&#x27;analyser efficacement le fichier CSV lorsque nous passons à l&#x27;étape de l&#x27;ingestion en masse.</p><p>3. Créez un index avec la correspondance appropriée. Bien qu&#x27;Elasticsearch puisse déduire automatiquement les types de champs avec le <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dynamic">mappage dynamique</a>, nous voulons être explicites ici pour que chaque statut soit traité comme un champ numérique. Ceci est important car nous utiliserons ces champs pour les agrégations par la suite. Nous voulons également utiliser le type <code>float </code>pour les statistiques telles que les points, les rebonds, etc., afin de nous assurer que nous incluons des valeurs décimales. Enfin, nous voulons ajouter la propriété de mappage <code>dynamic: &#x27;strict&#x27;</code> afin qu&#x27;Elasticsearch ne mappe pas dynamiquement les champs non reconnus. <br/></p><pre><code>// Function to create an index with mappings
async function createIndex() {
    try {
        // Check if the index already exists
        const exists = await elasticClient.indices.exists({ index: indexName });

        if (exists) {
            console.log(`Index "${indexName}" already exists, deleting it now.`);
            await elasticClient.indices.delete({ index: indexName });
            console.log(`Deleted index "${indexName}".`);
        }
        // Create the index with mappings
        const response = await elasticClient.indices.create({
            index: indexName,
            body: {
                mappings: {
                    dynamic: 'strict', // Prevent dynamic mapping
                    properties: {
                        game_id: { type: 'integer' },
                        game_date: { type: 'date' },
                        player_id: { type: 'integer' },
                        player_full_name: { type: 'text' },
                        player_team_id: { type: 'integer' },
                        player_team_name: { type: 'text' },
                        home_team: { type: 'boolean' },
                        opponent_team_id: { type: 'integer' },
                        opponent_team_name: { type: 'text' },
                        points: { type: 'float' },
                        rebounds: { type: 'float' },
                        assists: { type: 'float' },
                        steals: { type: 'float' },
                        blocks: { type: 'float' },
                        fg_percentage: { type: 'float' },
                        minutes_played: { type: 'float' },
                    },
                },
            },
        });

        console.log('Index created:', response);
        return true;
    } catch (error) {
        console.error('Error creating index:', error);
        return false;
    }
}
</code></pre><p>4. Ajoutez la fonction permettant d&#x27;intégrer en masse les données CSV dans votre index Elasticsearch. À l&#x27;intérieur du bloc de code, nous sautons la ligne d&#x27;en-tête. Ensuite, divisez chaque ligne par une virgule et insérez-les dans l&#x27;objet document. Cette étape permet également de les nettoyer et de s&#x27;assurer qu&#x27;ils sont du bon type. Ensuite, nous plaçons les documents dans le tableau bulkBody avec les informations d&#x27;index, qui serviront de charge utile pour l&#x27;ingestion en masse dans Elasticsearch.</p><pre><code>async function bulkIngestCsv(filePath) {
    const readStream = fs.createReadStream(filePath);
    const rl = readline.createInterface({
        input: readStream,
        crlfDelay: Infinity,
    });

    const bulkBody = [];
    let lineNum = 0;

    //Skip the header line
    let headerLine = true;
    for await (const line of rl) {
        if (headerLine) {
            headerLine = false;
            continue;
        }
        lineNum++;

        // Split the line by comma and remove whitespace
        const [
            game_id,
            game_date,
            player_id,
            player_full_name,
            player_team_id,
            player_team_name,
            home_team,
            opponent_team_id,
            opponent_team_name,
            points,
            rebounds,
            assists,
            steals,
            blocks,
            fg_percentage,
            minutes_played,
        ] = line.split(',');

        // Create a document object
        const document = {
            game_id: parseInt(game_id),
            game_date: game_date.trim(),
            player_id: parseInt(player_id),
            player_full_name: player_full_name.trim(),
            player_team_id: parseInt(player_team_id),
            player_team_name: player_team_name.trim(),
            home_team: home_team.trim() === 'True', // Converts True/False into a boolean
            opponent_team_id: parseInt(opponent_team_id),
            opponent_team_name: opponent_team_name.trim(),
            points: parseFloat(points),
            rebounds: parseFloat(rebounds),
            assists: parseFloat(assists),
            steals: parseFloat(steals),
            blocks: parseFloat(blocks),
            fg_percentage: parseFloat(fg_percentage),
            minutes_played: parseFloat(minutes_played),
        };

        // Prepare the bulk operation format
        bulkBody.push({ index: { _index: indexName } });
        bulkBody.push(document);
    }

    console.log(`Parsed ${lineNum} lines from CSV`);
</code></pre><p>5. Ensuite, nous pouvons utiliser l&#x27;<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">API Bulk</a> d&#x27;Elasticsearch avec <code>elasticClient.bulk()</code> pour ingérer plusieurs documents en une seule demande. La gestion des erreurs ci-dessous est structurée de manière à vous indiquer le nombre de documents qui n&#x27;ont pas été ingérés et le nombre de documents qui ont été ingérés avec succès.</p><pre><code>try {
        // Perform the bulk request
        const response = await elasticClient.bulk({ body: bulkBody });

        if (response.errors) {
            console.log('Bulk Ingestion had some hiccups:');

            // Count successful vs failed operations
            let successCount = 0;
            let errorCount = 0;
            const errorDetails = [];

            response.items.forEach((item, index) => {
                const operation = item.index || item.create || item.update || item.delete;
                if (operation.error) {
                    errorCount++;
                    errorDetails.push({
                        document: index + 1,
                        error: operation.error,
                    });
                } else {
                    successCount++;
                }
            });

            console.log(`Successfully indexed: ${successCount} documents`);
            console.log(`Failed to index: ${errorCount} documents, here are the details`, errorDetails);

        } else {
            console.log(`Bulk Ingestion fully successful!`);
        }

    } catch (error) {
        console.error('Error performing bulk ingestion:', error);
    }
}
</code></pre><p>6. Exécutez la fonction <code>main()</code> ci-dessous pour exécuter séquentiellement les fonctions <code>createIndex()</code> et <code>bulkIngestCsv()</code>.</p><pre><code>// Run this function
async function main() {
    const result = await createIndex();
    if (!result) {
        console.error('Index setup failed. Aborting.');
        return;
    }

    await bulkIngestCsv(filePath);
    console.log('Bulk ingestion completed!');
}

main();
</code></pre><p>Si vous voyez un journal de console indiquant que l&#x27;ingestion en masse a réussi, effectuez une vérification rapide de votre index Elasticsearch pour voir si les documents ont effectivement été ingérés avec succès.</p><h4><strong>Étape 5 : Définition des agrégations Elasticsearch et consolidation</strong></h4><p>Ce sont les principales fonctions qui seront utilisées lorsque nous définirons les outils de l&#x27;agent IA afin de comparer les statistiques des joueurs entre eux.</p><p>1. Naviguez jusqu&#x27;au répertoire <code>backend/lib</code> et créez un fichier appelé <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticAggs.js">elasticAggs.js</a>.</p><p>2. Ajoutez la requête ci-dessous pour calculer les moyennes historiques d&#x27;un joueur contre un adversaire spécifique. Cette requête utilise un <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/full-text-search/filters">filtre</a> <code>bool</code> avec 2 conditions : l&#x27;une correspondant à <code>player_id</code> et l&#x27;autre à <code>opponent_team_id</code>, afin de récupérer uniquement les jeux pertinents. Nous n&#x27;avons pas besoin de renvoyer de documents, nous ne nous intéressons qu&#x27;aux agrégations, c&#x27;est pourquoi nous définissons <code>size:0</code>. Sous le bloc <code>aggs</code>, nous exécutons plusieurs <a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">agrégations</a> métriques en parallèle sur des champs tels que <code>points, rebounds, assists, steals, blocks</code> et <code>fg_percentage</code> pour calculer leurs valeurs moyennes. Les LLM peuvent être aléatoires dans leurs calculs et ce processus est déchargé sur Elasticsearch, ce qui garantit à notre assistant NBA AI l&#x27;accès à des données exactes.</p><pre><code>export async function getHistoricalAveragesAgainstOpponent(player_id, opponent_team_id) {
    try {
        //Query for Historical Averages
        const historicalQuery = await elasticClient.search({
            index: 'sample-nba-player-data', 
            size: 0,
            query: {
                bool: {
                    must: [
                        {
                            term: {
                                player_id: {
                                    value: player_id,
                                },
                            },
                        },
                        {
                            term: {
                                opponent_team_id: {
                                    value: opponent_team_id,
                                },
                            },
                        },
                    ],
                },
            },
            aggs: {
                avg_points: { avg: { field: 'points' } },
                avg_rebounds: { avg: { field: 'rebounds' } },
                avg_assists: { avg: { field: 'assists' } },
                avg_steals: { avg: { field: 'steals' } },
                avg_blocks: { avg: { field: 'blocks' } },
             avg_fg_percentage: { avg: { field: 'fg_percentage' } },
            },
        });

        return {
            points: historicalQuery.aggregations.avg_points.value || 0,
            rebounds: historicalQuery.aggregations.avg_rebounds.value || 0,
            assists: historicalQuery.aggregations.avg_assists.value || 0,
            steals: historicalQuery.aggregations.avg_steals.value || 0,
            blocks: historicalQuery.aggregations.avg_blocks.value || 0,
            fgPercentage: historicalQuery.aggregations.avg_fg_percentage.value || 0,
        };
    } catch (error) {
        console.error('Query error from getHistoricalAveragesAgainstOpponent function:', error);
        return { error: 'Queries failed in getting historical averages against opponent.' };
    }
}
</code></pre><p>3. Pour calculer les moyennes saisonnières d&#x27;un joueur contre un adversaire spécifique, nous utiliserons pratiquement la même requête que la requête historique. La seule différence dans cette requête est que le filtre <code>bool</code> est assorti d&#x27;une condition supplémentaire pour <code>game_date</code>. Le champ <code>game_date</code> doit se situer dans la fourchette de la saison NBA en cours. Dans ce cas, la fourchette est comprise entre <code>2024-10-01</code> et <code>2025-06-30</code>. Cette condition supplémentaire ci-dessous garantit que les agrégations qui suivent n&#x27;isoleront que les matchs de cette saison.<br/></p><pre><code>        {
                            range: {
                    //Range for this season, change to match current season
                                game_date: {
                                    gte: '2024-10-01',
                                    lte: '2025-06-30',
                                },
                            },
</code></pre><h4><strong>Étape 6 : Utilitaire de comparaison des joueurs</strong></h4><p>Pour que notre code reste modulaire et facile à maintenir, nous allons créer un fichier utilitaire qui consolide les fonctions d&#x27;aide aux métadonnées et les agrégations Elasticsearch. Il s&#x27;agit de l&#x27;outil principal utilisé par l&#x27;agent. Nous y reviendrons plus tard :</p><p>1. Créez un nouveau fichier <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/comparePlayers.js">comparePlayers.js</a> dans le répertoire <code>backend/lib</code>.</p><p>2. Ajoutez la fonction ci-dessous pour consolider les aides aux métadonnées et la logique d&#x27;agrégation Elasticsearch en une seule fonction qui alimente l&#x27;outil principal utilisé par l&#x27;agent.<br/></p><pre><code>import { playersByName } from '../data/playerAndTeamInfo.js';
import { teamsByName } from '../data/playerAndTeamInfo.js';
import { upcomingMatchups } from '../data/playerAndTeamInfo.js';
import { getHistoricalAveragesAgainstOpponent } from './elasticAggs.js';
import { getSeasonAveragesAgainstOpponent } from './elasticAggs.js';

//Simple helper functions to simulate API calls for player and team metadata. These reference the hardcoded values from playerAndTeamInfo.js in the data directory
export function getPlayerInfo(playerFullName) {
    return playersByName[playerFullName];
}

export function getTeamID(teamFullName) {
    return teamsByName[teamFullName];
}

export function getUpcomingMatchups(teamId) {
    return upcomingMatchups[teamId];
}

//Main function used by the 'playerComparisonTool' agent tool
export async function comparePlayersForNextMatchup(player1Name, player2Name) {
    //Get Player Info
    const player1Info = getPlayerInfo(player1Name);
    const player2Info = getPlayerInfo(player2Name);

    //Get upcoming matchups
    const player1NextGame = getUpcomingMatchups(player1Info.team_id)[0];
    const player2NextGame = getUpcomingMatchups(player2Info.team_id)[0];

    //Get season and historical averages against next opponent for player 1
    const player1SeasonAverages = await getSeasonAveragesAgainstOpponent(
        player1Info.player_id,
        player1NextGame.opponent_team_id
    );
    const player1HistoricalAverages = await getHistoricalAveragesAgainstOpponent(
        player1Info.player_id,
        player1NextGame.opponent_team_id
    );

    //Get season and historical averages against next opponent for player 2
    const player2SeasonAverages = await getSeasonAveragesAgainstOpponent(
        player2Info.player_id,
        player2NextGame.opponent_team_id
    );
    const player2HistoricalAverages = await getHistoricalAveragesAgainstOpponent(
        player2Info.player_id,
        player2NextGame.opponent_team_id
    );

    const player1 = {
        name: player1Name,
        playerId: player1Info.player_id,
        teamId: player1Info.team_id,
        nextOpponent: {
            teamId: player1NextGame.opponent_team_id,
            teamName: player1NextGame.opponent_team_name,
            home: player1NextGame.home,
        },
        stats: {
            seasonAverages: player1SeasonAverages,
            historicalAverages: player1HistoricalAverages,
        },
    };

    const player2 = {
        name: player2Name,
        playerId: player2Info.player_id,
        teamId: player2Info.team_id,
        nextOpponent: {
            teamId: player2NextGame.opponent_team_id,
            teamName: player2NextGame.opponent_team_name,
            home: player2NextGame.home,
        },
        stats: {
            seasonAverages: player2SeasonAverages,
            historicalAverages: player2HistoricalAverages,
        },
    };

    return [player1, player2];
}
</code></pre><h4><strong>Étape 7 : Création de l&#x27;agent</strong></h4><p>Maintenant que vous avez créé les échafaudages frontend et backend, ingéré les données du jeu NBA et établi une connexion à Elasticsearch, nous pouvons commencer à assembler toutes les pièces pour construire l&#x27;agent.</p><p><strong>Définition de l&#x27;agent</strong></p><p>1. Accédez au fichier <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/agents/index.ts">index.ts</a> dans le répertoire <code>backend/src/mastra/agents</code> et ajoutez la définition de l&#x27;agent. Vous pouvez spécifier des champs tels que :</p><ul><li><strong>Nom :</strong> Donnez à votre agent un nom qui sera utilisé comme référence lorsqu&#x27;il sera appelé sur le frontend.</li><li><strong>Instructions/Instructions du système : </strong>Une invite système donne au MLD le contexte initial et les règles à suivre pendant l&#x27;interaction. Il s&#x27;agit d&#x27;une invite similaire à celle que les utilisateurs envoient par l&#x27;intermédiaire de la boîte de dialogue, mais celle-ci est donnée avant toute entrée de l&#x27;utilisateur. Là encore, cela varie en fonction du modèle que vous choisissez.</li><li><strong>Modèle :</strong> Quel LLM utiliser (Mastra soutient OpenAI, Anthropic, les modèles locaux, etc.)</li><li><strong>Outils :</strong> Une liste de fonctions d&#x27;outils que l&#x27;agent peut appeler.</li><li><strong>Mémoire :</strong> (Facultatif) si nous voulons que l&#x27;agent se souvienne de l&#x27;historique des conversations, etc. Pour des raisons de simplicité, nous pouvons commencer sans mémoire persistante, bien que Mastra la prenne en charge.</li></ul><p></p><pre><code>import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { playerComparisonTool } from '../tools';

export const basketballAgent = new Agent({
    name: 'Basketball Agent',
    instructions: `
      You are a NBA Basketball expert.
      Your primary function is to compare two NBA players and recommend which one is the better fantasy pickup.

      Only compare players from the following list:
      - LeBron James
      - Stephen Curry
      - Jayson Tatum
      - Jaylen Brown
      - Nikola Jokic
      - Luka Doncic
      - Kyrie Irving
      - Anthony Davis
      - Kawhi Leonard
      - Russell Westbrook

      Input Handling Rules:
      - If the user asks about a player that is not on this list, respond with the list of available players for comparison.
      - If the user only inputs one player, ask the user to add another player from the list provided.
      - If the user inputs a player with the wrong spelling or capitalizations, infer from the list of available players provided.
      - IMPORTANT: If the user asks a question or asks you to generate a response about anything outside of basketball or the scope of this project, DO NOT answer and affirm you can only talk about basketball.

      Tool Usage:
      - Extract and standardize player names to match the list exactly.
      - Use the playerComparisonTool, passing both names as strings.
      - The tool will return an object with game information, stats, and analysis.

      Format your response using Markdown syntax. Use:

        Example output format:

       
        #### Next Game Info
        - ***LeBron James** vs Warriors, May 24 (Home)  
        - ***Stephen Curry** vs Lakers, May 24 (Away)


        #### Stats Comparison  
        \`\`\`  
        Stat                  LeBron James (vs Warriors)    Stephen Curry (vs Lakers)  
        --------------------  -----------------------------  ----------------------------  
        Historical Points     28.3                          30.3  
        Historical Assists    6.7                           8.7  
        Season Points         28.8                          23.3  
        Season Assists        6.2                           4.7  
        \`\`\`

        #### Fantasy Recommendation  
        Explain which player is the better fantasy pickup and why.
      
    `,
    model: openai('gpt-4o'),
    tools: { playerComparisonTool },
});
</code></pre><p><strong><br/>Définition des outils</strong></p><ol><li>Naviguez jusqu&#x27;au fichier <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/tools/index.ts">index.ts</a> dans le répertoire <code>backend/src/mastra/tools</code>.</li><li>Installez Zod à l&#x27;aide de la commande :</li></ol><pre><code>npm install zod</code></pre><p>3. Ajouter des définitions d&#x27;outils. Notez que nous importons la fonction dans le fichier <code>comparePlayers.js</code> en tant que fonction principale que l&#x27;agent utilisera lorsqu&#x27;il appellera cet outil. En utilisant la fonction <code>createTool()</code> de Mastra, nous enregistrerons notre <code>playerComparisonTool</code>. Les domaines concernés sont les suivants :</p><ul><li><code>id</code>: Il s&#x27;agit d&#x27;une description en langage naturel qui aide l&#x27;agent à comprendre ce que fait l&#x27;outil.</li><li><code>input schema</code>: Pour définir la forme de l&#x27;entrée de l&#x27;outil, Mastra utilise le schéma <a href="https://zod.dev/">Zod</a>, qui est une bibliothèque de validation de schéma TypeScript. Zod s&#x27;assure que l&#x27;agent saisit des données correctement structurées et empêche l&#x27;outil de s&#x27;exécuter si la structure de l&#x27;entrée ne correspond pas.</li><li><code>description</code>: Il s&#x27;agit d&#x27;une description en langage naturel qui aide l&#x27;agent à comprendre quand il doit appeler et utiliser l&#x27;outil.</li><li><code>execute</code>: La logique qui s&#x27;exécute lorsque l&#x27;outil est appelé. Dans notre cas, nous utilisons une fonction d&#x27;aide importée pour renvoyer des statistiques de performance.</li></ul><pre><code>import { comparePlayersForNextMatchup } from '../../../lib/comparePlayers.js'
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const playerComparisonTool = createTool({
    id: "Compare two NBA players",
    inputSchema: z.object({
        player1:z.string(),
        player2:z.string()
    }),
    description: "Use this tool to compare two players given in the user prompt.",
    execute: async ({ context: { player1, player2 } }) => {
        return await comparePlayersForNextMatchup(player1, player2);
      },
})</code></pre><p><strong>Ajout d&#x27;un logiciel intermédiaire pour gérer CORS</strong></p><p>Ajouter un middleware dans le serveur Mastra pour gérer <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">CORS</a>. On dit qu&#x27;il y a trois choses dans la vie qu&#x27;on ne peut pas éviter : la mort, les impôts, et pour les développeurs web, c&#x27;est CORS. En bref, le partage des ressources inter-origines est une fonction de sécurité du navigateur qui empêche le front-end d&#x27;envoyer des requêtes à un back-end fonctionnant sur un domaine ou un port différent. Même si nous exécutons le backend et le frontend sur localhost, ils utilisent des ports différents, ce qui déclenche la politique CORS. Nous devons ajouter l&#x27;intergiciel spécifié dans la <a href="https://mastra.ai/en/docs/server-db/middleware">documentation de Mastra</a> afin que notre backend autorise ces requêtes depuis le frontend.</p><p>1. Naviguez jusqu&#x27;au fichier <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/index.ts">index.ts</a> dans le répertoire <code>backend/src/mastra</code> et ajoutez la configuration pour CORS :</p><ul><li><code>origin: [&#x27;http://localhost:5173&#x27;]</code><ul><li>Autorise les demandes provenant uniquement de cette adresse (adresse par défaut de Vite)</li></ul></li><li><code>allowMethods: [&quot;GET&quot;, &quot;POST&quot;]</code><ul><li>Méthodes HTTP autorisées. La plupart du temps, il utilisera POST.</li></ul></li><li><code>allowHeaders: [&quot;Content-Type&quot;, &quot;Authorization&quot;, &quot;x-mastra-client-type, &quot;x-highlight-request&quot;, &quot;traceparent&quot;],</code><ul><li>Ils déterminent quels en-têtes personnalisés peuvent être utilisés dans les requêtes</li></ul></li></ul><p></p><pre><code>import { Mastra } from '@mastra/core/mastra';
import { basketballAgent } from './agents';

console.log('Starting Mastra server...');

export const mastra = new Mastra({
  agents: { basketballAgent },
  server:{
    timeout: 10 * 60 * 1000, // 10 minutes
    cors: {
      origin: ['http://localhost:5173'],
      allowMethods: ["GET", "POST"],
      allowHeaders: [
        "Content-Type",
        "Authorization",
        "x-mastra-client-type",
        "x-highlight-request",
        "traceparent",
      ],
      exposeHeaders: ["Content-Length", "X-Requested-With"],
      credentials: false,
    },
  },

});

console.log('Mastra server configured.'); // Log after server configuration
</code></pre><h4><strong>Étape 8 : Intégration de l&#x27;interface utilisateur</strong></h4><p>Ce composant React fournit une interface de chat simple qui se connecte à l&#x27;agent IA Mastra en utilisant le hook <a href="https://mastra.ai/en/docs/frameworks/agentic-uis/ai-sdk#using-the-usechat-hook">useChat()</a> de <code>@ai-sdk/react</code>. Nous allons également utiliser ce crochet pour afficher l&#x27;utilisation des jetons, les appels d&#x27;outils et pour rendre la conversation. Dans l&#x27;invite système ci-dessus, nous demandons également à l&#x27;agent de produire la réponse en format markdown, nous utiliserons donc <code>react-markdown</code> pour formater correctement la réponse.</p><p></p><p>1. Dans le répertoire frontend, installez le paquetage @ai-sdk/react pour utiliser le hook useChat().</p><pre><code>npm install @ai-sdk/react</code></pre><p>2. Dans le même répertoire, installez React Markdown pour que nous puissions formater correctement la réponse générée par l&#x27;agent.</p><pre><code>npm install react-markdown</code></pre><p>3. Mettre en œuvre <code>useChat()</code>. Ce hook va gérer l&#x27;interaction entre votre frontend et votre agent IA backend. Il gère l&#x27;état des messages, les entrées de l&#x27;utilisateur, l&#x27;état et vous donne des crochets de cycle de vie à des fins d&#x27;observabilité. Les options que nous transmettons sont les suivantes :</p><ul><li><code>api:</code> Ceci définit le point final de votre agent Mastra AI. Le port par défaut est le port 4111 et nous voulons également ajouter la route qui prend en charge les réponses en continu.</li><li><code>onToolCall</code>: Cette fonction s&#x27;exécute chaque fois que l&#x27;agent appelle un outil ; nous l&#x27;utilisons pour savoir quels outils notre agent appelle.</li><li><code>onFinish</code>: Cette opération s&#x27;exécute après que l&#x27;agent a fourni une réponse complète. Même si nous avons activé le streaming, <code>onFinish</code> sera toujours exécuté après la réception du message complet et non après chaque morceau. Ici, nous l&#x27;utilisons pour suivre l&#x27;utilisation de nos jetons. Cela peut s&#x27;avérer utile pour contrôler et optimiser les coûts de la gestion du cycle d&#x27;apprentissage tout au long de la vie.</li></ul><p>4. Enfin, nous nous rendons au composant <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/frontend/components/ChatUI.jsx">ChatUI.jsx</a> dans le répertoire <code>frontend/components</code> pour créer l&#x27;interface utilisateur de notre conversation. Ensuite, la réponse est enveloppée dans un composant <code>ReactMarkdown</code> afin de formater correctement la réponse de l&#x27;agent.</p><pre><code>import React, { useState } from 'react';
import { useChat } from '@ai-sdk/react';
import ReactMarkdown from 'react-markdown';

export default function ChatUI() {
    const [totalTokenUsage, setTotalTokenUsage] = useState(0);
    const [promptTokenUsage, setPromptTokenUsage] = useState(0);
    const [completionTokenUsage, setCompletionTokenUsage] = useState(0);
    const [toolsCalled, setToolsCalled] = useState([]);

    const { messages, input, handleInputChange, handleSubmit, status } = useChat({
        api: 'http://localhost:4111/api/agents/basketballAgent/stream', //Replace with your own endpoint for your agent
        id: 'my-chat-session',

        //Optional parameter to check agent tool calls
        onToolCall: ({ toolCall }) => {
            setToolsCalled((prev) => [...prev, toolCall.toolName]);
        },

        //Optional parameter to check token usages
        onFinish: (message, { usage }) => {
            setTotalTokenUsage((prev) => prev + usage.totalTokens);
            setPromptTokenUsage((prev) => prev + usage.promptTokens);
            setCompletionTokenUsage((prev) => prev + usage.completionTokens);
        },

        //Optional parameter for error handling
        onError: (error) => {
            console.error('Agent error:', error);
        },
    });

    return (
        <div>
            <div className="agent-info">
                <h4 className="stats-title">What's My Agent Doing?</h4>

                <div className="stats-box">
                    <strong className="stats-sub-title">Tools Called:</strong>
                    <ul className="tool-list">
                        {toolsCalled.map((tool, idx) => (
                            <li key={idx}>{tool}</li>
                        ))}
                        {toolsCalled.length === 0 && <li>No tools called yet.</li>}
                    </ul>

                    <div className="usage-stats">
                        <p>Prompt Token Usage: {promptTokenUsage}</p>
                        <p>Completion Token Usage: {completionTokenUsage}</p>
                        <p>Total Token Usage: {totalTokenUsage}</p>
                    </div>
                </div>
            </div>

            <strong>Conversation:</strong>
            <div className="convo-box">
                {messages.map((msg) => (
                    <div key={msg.id} className="message-item">
                        <strong className="message-role">{msg.role === 'assistant' ? 'Basketbot' : 'You'}:</strong>
                        <ReactMarkdown>{msg.content}</ReactMarkdown>
                    </div>
                ))}
            </div>

            <form onSubmit={handleSubmit}>
                <input
                    type="text"
                    value={input}
                    onChange={handleInputChange}
                    placeholder="Input two players you want to compare."
                    className="input-box"
                />
                <button type="submit" disabled={status === 'streaming'}>
                    {status === 'streaming' ? 'Thinking...' : 'Send'}
                </button>
            </form>
        </div>
    );
}</code></pre><h4><strong>Étape 9 : Exécution de l&#x27;application</strong></h4><p>Félicitations ! Vous êtes maintenant prêt à exécuter l&#x27;application. Suivez ces étapes pour démarrer le backend et le frontend.</p><ol><li>Dans une fenêtre de terminal, à partir du répertoire racine, naviguez jusqu&#x27;au répertoire backend et démarrez le serveur Mastra :</li></ol><pre><code>cd backend

npm run dev</code></pre><p>2. Dans une autre fenêtre de terminal, à partir du répertoire racine, naviguez jusqu&#x27;au répertoire frontend et démarrez l&#x27;application React :</p><p></p><pre><code>cd frontend

npm run dev</code></pre><p></p><p>3. Allez dans votre navigateur et naviguez jusqu&#x27;à :</p><p></p><p><a href="http://localhost:5173/">http://localhost:5173</a></p><p></p><p>Vous devriez voir l&#x27;interface de chat. Essayez les exemples suivants :</p><ul><li>&quot;Comparer LeBron James et Stephen Curry&quot;</li><li>&quot;Qui choisir entre Jayson Tatum et Luka Doncic ?&quot;</li></ul><p></p><h3><strong>Et maintenant ? Rendre l&#x27;agent plus intelligent</strong></h3><p>Pour rendre l&#x27;assistant plus agentive et les recommandations plus perspicaces, j&#x27;ajouterai quelques améliorations clés dans la prochaine itération.</p><p></p><p><strong>Recherche sémantique pour les nouvelles de la NBA</strong></p><p>Il y a une tonne de facteurs qui peuvent affecter les performances des joueurs, dont beaucoup n&#x27;apparaissent pas dans les statistiques brutes. Des choses comme les rapports sur les blessures, les changements de composition, ou même une analyse d&#x27;après-match, vous ne pouvez les trouver que dans des articles de presse. Pour saisir ce contexte supplémentaire, j&#x27;ajouterai des capacités de recherche sémantique afin que l&#x27;agent puisse retrouver des articles pertinents de la NBA et tenir compte de ce récit dans ses recommandations.</p><p></p><p><strong>Recherche dynamique avec le serveur Elasticsearch MCP</strong></p><p>Le protocole MCP (Model Context Protocol) devient rapidement la norme pour la connexion des agents aux sources de données. Je vais migrer la logique de recherche dans le serveur Elasticsearch MCP, qui permet à l&#x27;agent de construire dynamiquement des requêtes plutôt que de s&#x27;appuyer sur les fonctions de recherche prédéfinies que nous fournissons. Cela nous permet d&#x27;utiliser davantage de flux de travail en langage naturel et de réduire la nécessité de rédiger manuellement chaque requête de recherche. Pour en savoir plus sur le serveur Elasticsearch MCP et l&#x27;état actuel de l&#x27;écosystème <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">, cliquez ici.</a></p><p></p><p>Ces changements sont déjà en cours, restez à l&#x27;écoute !</p><h3><strong>Conclusion</strong></h3><p></p><p>Dans ce blog, nous avons construit un assistant RAG agentique qui fournit des recommandations personnalisées pour votre équipe de basket-ball fantasy en utilisant JavaScript, Mastra et Elasticsearch. Nous avons couvert :</p><ul><li><strong>Les principes fondamentaux de la RAG agentique</strong> et la manière dont la combinaison de l&#x27;autonomie d&#x27;un agent d&#x27;intelligence artificielle avec les outils permettant d&#x27;utiliser efficacement la RAG peut déboucher sur des agents plus nuancés et plus dynamiques.</li><li><strong>Elasticsearch </strong>et comment ses capacités de stockage de données et ses puissantes agrégations natives en font un partenaire idéal en tant que base de connaissances pour un LLM.</li><li><strong>Le </strong>cadre Mastra et la manière dont il simplifie la construction de ces agents pour les développeurs de l&#x27;écosystème JavaScript.</li></ul><p>Que vous soyez fanatique de basket-ball, que vous cherchiez à construire des agents d&#x27;intelligence artificielle, ou les deux comme moi, j&#x27;espère que ce blog vous a donné quelques éléments de base pour commencer. Le repo complet est disponible sur <a href="https://github.com/jdarmada/nba-ai-assistant-js">GitHub</a>, n&#x27;hésitez pas à le cloner et à le modifier. Maintenant, allez gagner cette ligue de fantasy !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Scripting Ruby dans Logstash]]></title>
            <description><![CDATA[Découvrez le plugin Logstash Ruby filter pour une transformation avancée des données dans votre pipeline Logstash.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/ruby-scripting-logstash</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/ruby-scripting-logstash</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Dai Sugimori]]></dc:creator>
            <pubDate>Tue, 24 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b1bcd63c033ccbde102c20ba3085f165f9289a71-1600x1000.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Logstash est un pipeline de traitement de données qui ingère des données provenant de sources multiples, les transforme et les envoie vers les destinations de votre choix. Les plugins de filtrage sont essentiels pour ce processus ; ils effectuent des opérations spécifiques sur vos données lorsqu&#x27;elles passent par le pipeline.</p><p>Logstash comprend plusieurs filtres intégrés pour les tâches courantes telles que l&#x27;analyse, l&#x27;enrichissement et la modification des données. Mais parfois, vous rencontrerez des scénarios qui nécessiteront une logique personnalisée allant au-delà de ce que ces filtres standard peuvent fournir. C&#x27;est là qu&#x27;intervient le <a href="https://www.elastic.co/docs/reference/logstash/plugins/plugins-filters-ruby">plugin Ruby filter</a>.</p><p><strong>Le plugin Ruby filter vous permet d&#x27;exécuter du code Ruby personnalisé directement dans votre pipeline Logstash.</strong> Lorsque les filtres standard ne suffisent pas, le filtre Ruby vous permet de gérer des transformations de données complexes, de mettre en œuvre une logique commerciale personnalisée ou d&#x27;intégrer des systèmes externes.</p><p>Dans ce blog, nous allons explorer comment utiliser les filtres Ruby, de l&#x27;utilisation basique à l&#x27;utilisation avancée.</p><h2>Quand utiliser le filtre Ruby ?</h2><p>En tant qu&#x27;architecte consultant d&#x27;Elastic, je vois souvent des clients utiliser Logstash pour le pipeline de traitement des données, même s&#x27;il ne s&#x27;agit pas aujourd&#x27;hui d&#x27;un moteur de traitement des données à la pointe de la technologie. Ils se heurtent souvent aux limites des filtres standard lorsqu&#x27;il s&#x27;agit de manipuler des données complexes ou d&#x27;appliquer une logique personnalisée. Dans ce cas, le filtre Ruby peut aider à surmonter ces difficultés.</p><p>Le filtre Ruby est utile lorsque les filtres standard de Logstash ne peuvent pas répondre à vos besoins spécifiques. Voici quelques cas d&#x27;utilisation courants :</p><ul><li><strong>Manipulation de données imbriquées en profondeur</strong>: Modifier des structures JSON complexes, des tableaux dans des tableaux, ou restructurer dynamiquement des données en fonction de leur contenu.</li><li><strong>Traitement avancé des chaînes de caractères</strong>: Analyse et extraction de données structurées à partir de textes non structurés</li><li><strong>Mise en œuvre d&#x27;une logique d&#x27;entreprise complexe</strong>: Créer des transformations personnalisées qui nécessitent une logique conditionnelle, des boucles ou des calculs complexes.</li></ul><h2>Utilisation de base</h2><p>Commençons par un exemple simple pour comprendre le fonctionnement du filtre Ruby.</p><h3>Configuration du filtre Ruby</h3><p>Lorsque vous créez un pipeline Logstash, vous devez placer le fichier de configuration dans le répertoire <code>/etc/logstash/conf.d</code>. Alternativement, vous pouvez utiliser l&#x27;option <code>-f</code> pour spécifier le chemin vers le fichier de configuration lorsque vous démarrez Logstash manuellement, afin que vous puissiez expérimenter vos pipelines facilement.</p><pre><code>$ ./bin/logstash -f /path/to/your_pipeline.conf</code></pre><p>Le fichier de configuration doit avoir une extension <code>.conf</code>.</p><p>Pour utiliser le filtre Ruby, définissez un filtre <code>ruby</code> dans la section filter de votre fichier de configuration du pipeline Logstash (*.conf). Voici un exemple de base :</p><pre><code>filter {
  ruby {
    code => "
      event.set('new_field', 'Hello from Ruby!')
    "
  }
}</code></pre><p>Ce filtre Ruby en ligne définit une instance de filtre Ruby dans votre configuration Logstash. Le paramètre <code>code</code> fournit le script Ruby en ligne que Logstash exécutera pour chaque événement traité par ce filtre. Dans ce script, il existe une variable <code>event</code> qui représente l&#x27;événement lui-même. L&#x27;objet événement contient les données originales envoyées à Logstash et tous les champs supplémentaires créés lors des étapes de filtrage de Logstash. Vous pouvez accéder à ces champs via l&#x27;API Logstash Event telle que <code>event.get()</code> et <code>event.set()</code>. Dans cet exemple de code, <code>event.set(&#x27;new_field&#x27;, &#x27;Hello from Ruby!&#x27;)</code> attribue à un nouveau champ nommé <code>new_field</code> la valeur de chaîne <code>Hello from Ruby!</code>. Vous pouvez ajouter tout autre code dans ce bloc <code>code</code> si nécessaire.</p><p>Notez que cet objet <code>event</code> n&#x27;est pas un objet de hachage Ruby habituel, bien qu&#x27;il agisse comme un conteneur de données de type clé-valeur. Consultez <a href="https://www.elastic.co/docs/reference/logstash/event-api">la documentation officielle</a> pour en savoir plus sur l&#x27;API des événements.</p><h3>Externaliser le script Ruby</h3><p>Pour les transformations simples, le code Ruby en ligne est pratique. Mais pour une logique complexe ou des fonctions réutilisables, il est recommandé de déplacer le code dans un script Ruby externe. Cela permet d&#x27;améliorer la maintenabilité et de conserver une configuration propre du pipeline Logstash.</p><p>Tout d&#x27;abord, créez un script Ruby et enregistrez-le sous <code>my_ruby_script.rb</code>. Le script doit définir une méthode <code>filter</code> qui traite l&#x27;événement. Elle prend en argument un objet événement qui représente l&#x27;événement en cours de traitement. La méthode <code>filter</code> doit renvoyer un tableau d&#x27;événements à émettre. Pour supprimer l&#x27;événement, renvoyer un tableau vide.</p><p>Par exemple, le script suivant lit la rubrique <code>message</code>, calcule sa longueur et stocke le résultat dans une nouvelle rubrique appelée <code>message_length</code>.</p><pre><code>def register(params)
  # This method is called when the plugin is loaded.
  # You can use it to initialize any instance variables or perform setup tasks.
end

def filter(event)
  message = event.get('message')

  if message
    event.set('message_length', message.length)
  end

  return [event]
end</code></pre><p>Ensuite, définissez la configuration du filtre Ruby pour qu&#x27;il fasse référence au script à l&#x27;aide de l&#x27;option <code>path</code>. Cela indique à Logstash de charger et d&#x27;exécuter le script externe. Lors de l&#x27;utilisation de scripts externes, assurez-vous que le fichier existe et que les autorisations sont correctes.</p><pre><code>filter {
  ruby {
    path => "/path/to/my_ruby_script.rb"
  }
}</code></pre><p>Maintenant, chaque événement est transmis à la méthode <code>filter</code> dans <code>my_ruby_script.rb</code> et est traité par elle.</p><p>Cette approche vous permet de gérer plus efficacement une logique complexe, ce qui facilite les tests, le débogage et la réutilisation de votre code Ruby.</p><h2>Utilisation avancée</h2><p>Dans cette section, nous allons explorer quelques exemples avancés d&#x27;utilisation du filtre Ruby dans Logstash. Ces exemples montrent comment effectuer des transformations de données, enrichir des événements et mettre en œuvre une logique personnalisée à l&#x27;aide de Ruby.</p><h3>Manipulation de structures de données imbriquées</h3><p>Un événement Logstash est la structure de données centrale que Logstash traite. Il peut contenir différents champs, y compris des structures de données imbriquées telles que des tableaux et des hachages. Le filtre Ruby permet de manipuler facilement ces structures imbriquées.</p><p>Le filtre Ruby peut gérer des structures de données imbriquées, telles que des hachages et des tableaux, ce qui permet de modifier ou d&#x27;ajouter des champs dans ces structures. Cette fonction est utile lorsqu&#x27;il s&#x27;agit de traiter des formats de données complexes tels que JSON.</p><pre><code>input {
  generator {
    lines => [
      '{"nested": {"key1": "value1", "key2": "value2"}}'
    ]
    count => 1
    codec => "json"
    ecs_compatibility => "disabled"
  }
}

filter {
  ruby {
    code => "
      nested_data = event.get('nested')

      if nested_data.is_a?(Hash)
        nested_data['key3'] = 'value3'
        event.set('nested', nested_data)
      end
    "
  }
}

output {
  stdout { codec => rubydebug }
}</code></pre><p>Cet exemple inclut un objet JSON imbriqué dans les données d&#x27;entrée. Le filtre Ruby modifie les données imbriquées en ajoutant une nouvelle paire clé-valeur. Ce type de manipulation des données imbriquées n&#x27;est pas possible avec les filtres Logstash standard, ce qui fait du filtre Ruby une option pratique pour les structures de données complexes.</p><h3>Diviser un événement unique en plusieurs événements</h3><p>Les filtres Ruby peuvent également être utilisés pour diviser un événement unique en plusieurs événements. Ceci est utile lorsque vous avez un événement unique contenant un tableau d&#x27;éléments et que vous souhaitez créer des événements distincts pour chaque élément.</p><p>Notez que ni le pipeline d&#x27;acquisition d&#x27;Elasticsearch ni les processeurs de Beats/Elastic Agent ne prennent en charge le fractionnement des événements. C&#x27;est l&#x27;un des cas d&#x27;utilisation les plus importants pour Logstash.</p><h4>Avec filtre divisé</h4><p>Vous pouvez utiliser le filtre <code>split</code> pour diviser un événement en plusieurs événements sur la base d&#x27;un champ spécifié. Toutefois, si vous devez effectuer des transformations ou des opérations logiques supplémentaires pendant le fractionnement, vous pouvez utiliser le filtre Ruby en combinaison avec le filtre de fractionnement.</p><p>Dans l&#x27;exemple suivant, nous avons un flux RSS sous la forme d&#x27;une seule ligne de texte XML. Il contient plusieurs éléments <code>&lt;item&gt;</code>. Le filtre Ruby est utilisé pour extraire les éléments <code>&lt;item&gt;</code> du XML et les stocker dans un nouveau champ appelé <code>items</code>. Le filtre de division est ensuite utilisé pour diviser l&#x27;événement en plusieurs événements sur la base du champ <code>items</code>.</p><pre><code>input {
  generator {
    lines => [
      '<rss version="2.0"><channel><title>Sample RSS</title><item><title>Article 1</title><link>http://example.com/1</link><description>Desc 1</description></item><item><title>Article 2</title><link>http://example.com/2</link><description>Desc 2</description></item></channel></rss>'
    ]
    count => 1
    codec => "plain"
    ecs_compatibility => "disabled"
  }
}

filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  ruby {
    code => "event.set('items', event.get('[rss][channel][item]')) if event.get('[rss][channel][item]')"
  }
  split {
    field => "items"
  }
  ruby {
    code => "
      item = event.get('items')
      event.set('title', item['title']) if item['title']
      event.set('link', item['link']) if item['link']
      event.set('description', item['description']) if item['description']
    "
  }
  mutate {
    remove_field => ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}

output {
  stdout { codec => rubydebug }
}</code></pre><p>Le résultat sera le suivant :</p><pre><code>{
          "title" => "Article 1",
           "link" => "http://example.com/1",
    "description" => "Desc 1"
}
{
          "title" => "Article 2",
           "link" => "http://example.com/2",
    "description" => "Desc 2"
}</code></pre><p>Comme vous l&#x27;avez peut-être remarqué, le filtre <code>ruby</code> n&#x27;est pas indispensable dans ce cas. Le filtre <code>split</code> peut être utilisé pour diviser l&#x27;événement en plusieurs événements sur la base du champ <code>items</code>, et le filtre <code>mutate</code> peut être utilisé pour supprimer les champs inutiles. Toutefois, si vous devez effectuer des transformations ou des opérations logiques supplémentaires pendant le fractionnement, vous pouvez utiliser le filtre Ruby.</p><h4>Utiliser un script Ruby en ligne</h4><p>Vous pouvez également utiliser un script Ruby en ligne pour diviser un événement unique en plusieurs événements en utilisant la méthode <code>event.clone</code> et la méthode <code>new_event_block variable</code>, telle que <code>new_event_block.call(new_event)</code>. Cela vous permet de créer de nouveaux événements basés sur l&#x27;événement original tout en préservant ses données.</p><p>Voici un exemple d&#x27;utilisation du filtre Ruby pour diviser un événement unique en plusieurs événements. L&#x27;entrée et la sortie sont les mêmes que dans l&#x27;exemple précédent.</p><pre><code>filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  ruby {
    code => "
      items = event.get('[rss][channel][item]')
      if items.is_a?(Array)
        items.each do |item|
          new_event = event.clone
          new_event.set('title', item['title'])
          new_event.set('link', item['link'])
          new_event.set('description', item['description'])
          new_event_block.call new_event
        end
        event.cancel
      elsif items.is_a?(Hash)
        event.set('title', items['title'])
        event.set('link', items['link'])
        event.set('description', items['description'])
      end
    "
  }
  mutate {
    remove_field => ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}</code></pre><h4>Utiliser un script Ruby externe</h4><p>Vous pouvez également utiliser un script Ruby externe pour diviser un événement unique en plusieurs événements.</p><p>Fichier de configuration :</p><pre><code>filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  ruby {
    path => "path/to/ruby/split_event.rb"
  }
  mutate {
    remove_field => ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}</code></pre><p>Le script Ruby doit être externalisé en tant que <code>split_event.rb</code>:</p><pre><code>def filter(event)
  items = event.get('[rss][channel][item]')
  events = []
  if items.is_a?(Array)
    items.each do |item|
      new_event = event.clone
      new_event.set('title', item['title'])
      new_event.set('link', item['link'])
      new_event.set('description', item['description'])
      events << new_event
    end
    return events
  elsif items.is_a?(Hash)
    event.set('title', items['title'])
    event.set('link', items['link'])
    event.set('description', items['description'])
    return [event]
  else
    return []
  end
end</code></pre><p>N&#x27;oubliez pas que la méthode <code>filter</code> doit renvoyer un tableau d&#x27;événements. Vous pouvez renvoyer plusieurs événements en clonant un objet événement entrant et en l&#x27;ajoutant au tableau, ou vous pouvez renvoyer un seul événement sous la forme d&#x27;un tableau à un seul élément.</p><pre><code>return events
# or
# return [event]</code></pre><p>Cela vous permet de diviser un événement unique en plusieurs événements.</p><h3>Exécuter des commandes externes et analyser leurs résultats</h3><p>Le plugin Logstash exec input vous permet d&#x27;exécuter des commandes externes et leur sortie sera un événement de Logstash. La sortie de la commande sera stockée dans le champ <code>message</code> de l&#x27;événement.</p><p>Habituellement, la sortie des commandes système est lisible par l&#x27;homme, mais n&#x27;est pas structurée en JSON ou dans d&#x27;autres formats que Logstash peut facilement analyser. Pour ce faire, vous pouvez utiliser le filtre Ruby pour analyser la sortie et en extraire les informations.</p><p>Voici un exemple d&#x27;utilisation du plugin d&#x27;entrée <code>exec</code> pour exécuter la commande <code>ps -ef</code>, qui répertorie tous les processus en cours d&#x27;exécution sur un système de type Unix. La sortie sera analysée par le filtre Ruby afin d&#x27;extraire les informations pertinentes sur chaque processus.</p><pre><code>input {
  exec {
    command => "ps -ef"
    interval => 60
  }
}

filter {
  ruby {
    code => '
      processes = []
      lines = event.get("message").split("\n")  
      lines.each_with_index do |line, index|
        # Skip header line and empty lines
        next if index == 0 || line.strip.empty?
        entry = nil
        
        # Use regex to match the ps -ef output format more flexibly
        # This pattern accounts for variable spacing and different time formats
        if line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+([\d:]+\.?\d*)\s+(.+)$/
          uid, pid, ppid, c, stime, tty, time, cmd = $1, $2, $3, $4, $5, $6, $7, $8
          
          entry = {
            "UID" => uid,
            "PID" => pid,
            "PPID" => ppid,
            "C" => c,
            "STIME" => stime,
            "TTY" => tty,
            "TIME" => time,
            "CMD" => cmd.strip
          }
        elsif line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/
          # Fallback pattern for lines that might not match the exact format
          # Split the remaining part more carefully
          uid, pid, ppid, c, remainder = $1, $2, $3, $4, $5
          
          # Split remainder into STIME, TTY, TIME, CMD
          parts = remainder.strip.split(/\s+/, 4)
          if parts.length >= 4
            stime, tty, time, cmd = parts[0], parts[1], parts[2], parts[3]
            
            entry = {
              "UID" => uid,
              "PID" => pid,
              "PPID" => ppid,
              "C" => c,
              "STIME" => stime,
              "TTY" => tty,
              "TIME" => time,
              "CMD" => cmd
            }
          end
        end
        if entry && entry["UID"] == "0"
          original_line = line.strip
          entry["original_line"] = original_line if original_line.length > 0
          processes.push(entry)
        end
      end
      event.set("processes", processes)
      event.remove("message")
      event.remove("event")
    '
  }
}

output {
  stdout { codec => rubydebug }
}</code></pre><p>Cet exemple utilise le plugin d&#x27;entrée <code>exec</code> pour exécuter la commande <code>ps -ef</code> toutes les 60 secondes. Le filtre Ruby traite la sortie, en extrayant les champs pertinents tels que UID, PID, PPID, l&#x27;utilisation du CPU (C), l&#x27;heure de démarrage (STIME), TTY, le temps total du CPU (TIME), et la commande (CMD) exécutée. Cela fonctionne bien dans mon environnement macOS, mais il se peut que vous deviez ajuster les motifs des expressions rationnelles pour qu&#x27;ils correspondent au format de sortie de la commande <code>ps -ef</code> sur votre système.</p><h3>Utiliser les bibliothèques intégrées</h3><p>Le plugin de filtrage Ruby vous permet d&#x27;utiliser des bibliothèques Ruby intégrées, qui peuvent s&#x27;avérer très utiles pour diverses tâches. Par exemple, vous pouvez utiliser la bibliothèque <code>json</code> pour analyser les chaînes JSON ou la bibliothèque <code>date</code> pour manipuler les dates.</p><p>Voici un exemple d&#x27;utilisation de la bibliothèque <code>json</code> pour analyser une chaîne JSON stockée dans un champ :</p><pre><code>require 'json'

def filter(event)
  json_string = event.get('message')
  parsed_json = JSON.parse(json_string)
  event.set('parsed_json', parsed_json)
  return [event]
end</code></pre><p>Pour éviter d&#x27;avoir besoin de la bibliothèque à chaque fois, vous devriez externaliser votre code Ruby afin d&#x27;utiliser l&#x27;instruction <code>require</code> au début de votre script de filtrage Ruby. Cela chargera la bibliothèque une fois et la rendra disponible pour une utilisation dans votre script.</p><p>Pour vérifier quelles sont les bibliothèques disponibles dans votre environnement, vous pouvez dresser la liste des bibliothèques intégrées en exécutant le code suivant dans le filtre Ruby :</p><pre><code>Gem.loaded_specs.sort_by { |name, _| name }.each do |name, spec|
  puts "#{name}: #{spec.version}"
end</code></pre><p><strong>Note : </strong>Les bibliothèques intégrées ne sont pas officiellement supportées par Logstash, et leur comportement peut changer ou elles peuvent ne pas être disponibles dans les versions futures. Utilisez-les à vos risques et périls.</p><h2>Conclusion</h2><p>Le filtre Logstash Ruby vous permet de personnaliser et d&#x27;étendre les capacités de vos pipelines Logstash. Dans ce billet, nous avons couvert les bases de l&#x27;utilisation du filtre Ruby et fourni des exemples d&#x27;utilisation avancée.</p><p>En tirant parti du filtre Ruby, vous pouvez effectuer des tâches de traitement de données complexes qui nécessitent une logique personnalisée ou des manipulations avancées. Que vous travailliez avec des structures de données imbriquées, que vous fractionniez des événements ou que vous analysiez et convertissiez du texte complexe/non structuré en JSON structuré, le filtre Ruby offre la flexibilité nécessaire pour répondre à vos besoins spécifiques.</p><p>Nous espérons que ce guide vous a apporté les connaissances et l&#x27;inspiration nécessaires pour explorer tout le potentiel du filtre Logstash Ruby. Bonne lecture !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment déployer Elasticsearch sur GCP GKE Autopilot]]></title>
            <description><![CDATA[Découvrez comment déployer un cluster Elasticsearch sur GCP en utilisant GKE Autopilot et ECK pour une configuration Elasticsearch partiellement gérée.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/eck-gke-autopilot</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/eck-gke-autopilot</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Eduard Martin]]></dc:creator>
            <pubDate>Thu, 19 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d9c462fe63011356671479ccfedd435eec1ede52-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans cet article, nous allons découvrir comment déployer Elasticsearch sur Google Cloud Kubernetes (GKE) en utilisant Autopilot.</p><p>Pour Elasticsearch, nous allons utiliser <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes </a>(ECK), qui est l’opérateur Kubernetes officiel d’Elasticsearch et qui simplifie l’orchestration des déploiements Kubernetes pour tous les composants Elastic Stack.</p><p>Pour en savoir plus sur la façon de déployer des clusters Elasticsearch sur différentes infrastructures GCP, vous pouvez consulter nos articles de prise en main pour <a href="https://www.elastic.co/search-labs/blog/elasticsearch-gpc-google-compute-engine">Google Cloud Compute</a> et <a href="https://www.elastic.co/search-labs/blog/deploy-elastic-gcp-marketplace">Google Cloud Marketplace</a>.</p><h2>Effort de déploiement d&#x27;Elasticsearch</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/56b54d718dcff9af9050873c41fdf738074851da-1428x582.png" style="width: 100%;" /><h3>Qu&#x27;est-ce que GKE Autopilot ?</h3><p><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview?hl=es-419"><strong>Google Kubernetes Engine (GKE) Autopilot</strong></a> offre une expérience Kubernetes entièrement gérée où Google prend en charge la configuration du cluster, la gestion des nœuds, la sécurité et la mise à l&#x27;échelle, tandis que les développeurs se concentrent sur le déploiement des applications, permettant aux équipes de passer du code à la production en quelques minutes grâce à de bonnes pratiques intégrées.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bbf6de63fa0a199326352f521cb22654818799f6-1600x958.png" style="width: 100%;" /><h2>Quand utiliser l’ECK dans Google Cloud ?</h2><p>Elastic Cloud on Kubernetes (ECK) est parfaitement adapté aux entreprises qui disposent d&#x27;une infrastructure Kubernetes existante et souhaitent déployer Elasticsearch avec des fonctionnalités avancées telles que des rôles de nœuds dédiés, la haute disponibilité et l&#x27;automatisation.</p><h2>Comment configurer ECK dans Google Cloud ?</h2><p>1. Connectez-vous à la <a href="https://console.cloud.google.com">Google Cloud Console</a>.</p><p>2. <strong>En haut à droite</strong>, cliquez<strong> </strong>sur le bouton <strong>Cloud Shell</strong> pour accéder à la console, et déployez le cluster GKE à partir de là. Sinon, vous pouvez utiliser la <a href="https://cloud.google.com/cli">CLI gcloud</a>.</p><p><strong><em>N&#x27;oubliez pas de remplacer l&#x27;identifiant du projet par le vôtre pendant le tutoriel.</em></strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/999b03861d4fe44f360ab4c7e2616e1dc10cf182-1558x1248.png" style="width: 100%;" /><p>3. Activez l&#x27;<a href="https://console.cloud.google.com/flows/enableapi?apiid=container.googleapis.com">API Google Kubernetes Engine</a>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03827d3dc0e987c019e7747d33e7c01920047beb-911x246.png" style="width: 100%;" /><p>Cliquez sur <strong><em>Suivant</em></strong>.</p><p>L&#x27;API Kubernetes Engine devrait désormais apparaître comme activée lorsque vous recherchez cette API.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5cd46f0333086bcb31b80cf9c08a469b449ec0f-640x250.png" style="width: 100%;" /><p>4. Dans Cloud Shell, créez un cluster Autopilot. Nous le nommerons autopilot-cluster-1, et remplacerons également autopilot-test par l&#x27;identifiant de votre projet.</p><pre><code>gcloud beta container --project "autopilot-test-457216" clusters create-auto "autopilot-cluster-1" --region "us-central1" --release-channel "regular" --tier "standard" --enable-ip-access --no-enable-google-cloud-access --network "projects/autopilot-test-457216/global/networks/default" --subnetwork "projects/autopilot-test-457216/regions/us-central1/subnetworks/default" --cluster-ipv4-cidr "/17" --binauthz-evaluation-mode=DISABLED</code></pre><p>5. Attendez qu le cluster soit prêt. La création prend environ 10 minutes.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/81fbc45ba56d0f16ba42724cb8ae45e60b327dbc-1581x258.png" style="width: 100%;" /><p>Un message de confirmation s’affiche après avoir correctement configuré le cluster.</p><p>6. Configurez l&#x27;accès à la ligne de commande kubectl.</p><pre><code>gcloud container clusters get-credentials autopilot-cluster-1 --region us-central1 --project autopilot-test-457216</code></pre><p>Vous devriez voir :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b0beba1ee00ce9029f586ee32693fc2aa58c7f65-3442x142.png" style="width: 100%;" /><p><em>Entrée kubeconfig générée pour autopilot-cluster-1</em></p><p>7. Installez l’opérateur <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes</a> (ECK).</p><pre><code># Install ECK Custom Resource Definitions
kubectl create -f https://download.elastic.co/downloads/eck/2.16.1/crds.yaml

# Install the ECK operator
kubectl apply -f https://download.elastic.co/downloads/eck/2.16.1/operator.yaml</code></pre><p>8. Créons une instance Elasticsearch à nœud unique avec les valeurs par défaut.</p><p>Si vous souhaitez consulter quelques recettes pour différentes configurations, suivez <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/recipes">ce lien</a>.</p><p>Gardez à l’esprit que si vous ne spécifiez pas de <code>storageClass</code>, ECK utilisera celui défini par défaut, qui, pour GKE, est <code>standard-rwo</code> et qui utilise le <a href="https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/gce-pd-csi-driver?cloudshell=true">disque persistant Compute Engine CSI Driver</a>, et créera un volume de 1 Go avec celui-ci.</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
EOF</code></pre><p>Nous avons désactivé <code>nmap</code>, car la machine GKE par défaut a une valeur de <code>vm.max_map_count</code> trop basse. Il est recommandé de ne pas la désactiver pour la production, mais d&#x27;augmenter la valeur de <code>vm.max_map_count</code>. Vous pouvez en savoir plus sur la façon de <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/virtual-memory">procéder ici</a>.</p><p>9. Déployons également un cluster Kibana à nœud unique. Pour Kibana, nous ajouterons un équilibreur de charge qui nous donnera une adresse IP externe que nous pourrons utiliser pour accéder à Kibana depuis notre appareil.</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      metadata:
        annotations:
          cloud.google.com/l4-rbs: "enabled"
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
EOF</code></pre><p>Notez l&#x27;annotation : </p><p><code>cloud.google.com/l4-rbs: &quot;enabled&quot;</code></p><p><strong><em>Cette adresse est très importante car elle indique à Autopilot de fournir un équilibreur de charge accessible au public. Si l&#x27;adresse IP n&#x27;est pas définie, l&#x27;équilibreur de charge sera interne.</em></strong></p><p>10. Vérifiez que vos pods sont en cours d&#x27;exécution.</p><pre><code>kubectl get pods</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99da1270581a137683770efdb9c6e1577ec9fc01-3150x442.png" style="width: 100%;" /><p>11. Vous pouvez également <code>run kubectl get elasticsearch</code> et <code>kubectl get kibana</code> pour des statistiques plus spécifiques, telles que la version d&#x27;Elasticsearch, les nœuds et l&#x27;état d&#x27;intégrité.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/86234f307970fd5f78b8acd41496e8cc89ff82d3-3414x326.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/29160286ccc88928734c8ea11b1923db8e85d49d-3142x318.png" style="width: 100%;" /><p>12. Accédez à vos services.</p><pre><code>kubectl get svc</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ed1a07314b84a99b4aa1fec3db4b9badeb9587ee-3446x610.png" style="width: 100%;" /><p>Cela vous indiquera l&#x27;URL externe de Kibana sous EXTERNAL-IP. Le provisionnement de l&#x27;équilibreur de charge prend quelques minutes. <strong><em>Copiez la valeur de EXTERNAL-IP.</em></strong></p><p>13. Obtenez le mot de passe Elasticsearch pour l’utilisateur &quot;elastic&quot; :</p><pre><code>kubectl get secret quickstart-es-elastic-user -o=jsonpath='{.data.elastic}' | base64 --decode</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ab5054219216ebc15fc0d96e27605aaf13b720c6-3448x210.png" style="width: 100%;" /><p>14. <strong>Accédez à Kibana</strong> depuis votre navigateur :</p><ul><li>URL : https://&lt;EXTERNAL_IP&gt;:5601</li><li>Nom d&#x27;utilisateur:elastic</li><li>Mot de passe : 28Pao50lr2GpyguX470L2uj5 (de l’étape précédente)</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/47cbe88dc14db64db3a256f3f7504cc86a843475-463x503.png" style="width: 100%;" /><p>15. En accédant depuis votre navigateur, l&#x27;écran de bienvenue apparaît.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a75c151c0144b7efe2b730698c0ed0156fa9b16a-1600x1005.png" style="width: 100%;" /><p>Si vous souhaitez modifier les spécifications du cluster Elasticsearch, comme modifier ou redimensionner les nœuds, vous pouvez réappliquer le fichier manifeste yml avec les nouveaux paramètres :</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
    - name: default
      count: 2
      config:
        node.store.allow_mmap: false
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              resources:
                requests:
                  memory: 1.5Gi
                  cpu: 2
                limits:
                  memory: 1.5Gi
                  cpu: 2
EOF</code></pre><p>Dans cet exemple, nous allons ajouter un nœud supplémentaire et modifier la mémoire vive et le processeur. Comme vous pouvez le voir, <code>kubectl get elasticsearch</code> affiche maintenant 2 nœuds :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/48b475622cc48890bff8105d151f2cbde28d7021-3418x298.png" style="width: 100%;" /><p>Il en va de même pour Kibana :</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      metadata:
        annotations:
          cloud.google.com/l4-rbs: "enabled"
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
  podTemplate:
    spec:
      containers:
        - name: kibana
          env:
            - name: NODE_OPTIONS
              value: "--max-old-space-size=1024"
          resources:
            requests:
              memory: 0.5Gi
              cpu: 0.5
            limits:
              memory: 1Gi
              cpu: 1
EOF</code></pre><p>Nous pouvons ajuster le processeur/la RAM du conteneur ainsi que l&#x27;utilisation de la mémoire <a href="https://nodejs.org/">Node.js </a>(<a href="https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib">max-old-space-size</a>).</p><p>N&#x27;oubliez pas que les <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/volume-claim-templates">revendications de volume existantes ne peuvent pas être réduites</a>. Après avoir appliqué la mise à jour, l&#x27;opérateur effectuera les modifications avec un temps de perturbation minimal.</p><p>N’oubliez pas de supprimer le cluster une fois les tests terminés pour éviter des coûts inutiles.</p><pre><code>gcloud container clusters delete autopilot-cluster-1</code></pre><h2>Étapes suivantes</h2><p>Si vous souhaitez en savoir plus sur Kubernetes et Google Kubernetes Engine, consultez ces articles :</p><ul><li><a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud sur Kubernetes | Elastic Docs</a></li><li><a href="https://cloud.google.com/blog/products/containers-kubernetes/introducing-gke-autopilot">Présentation de GKE Autopilot | Blog Google Cloud</a></li><li><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview">Aperçu d&#x27;Autopilot | Google Kubernetes Engine (GKE)</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Utiliser Azure LLM Functions avec Elasticsearch pour des requêtes plus intelligentes]]></title>
            <description><![CDATA[Explorez un exemple d'application de recherche immobilière qui utilise Azure Gen AI LLM Functions avec Elasticsearch pour fournir des résultats de recherche hybrides flexibles. Voyez étape par étape comment configurer et exécuter l'application d'exemple dans GitHub Codespaces.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/azure-llm-functions-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/azure-llm-functions-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jonathan Simon]]></dc:creator>
            <pubDate>Fri, 13 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1431b90c7e00de06574c1e33c44a2e89296c824e-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Précision. Quand c&#x27;est important, c&#x27;est très important. Lorsque vous recherchez quelque chose de spécifique, la précision est très importante. Cependant, il arrive qu&#x27;une requête trop précise ne donne aucun résultat. Il est donc avantageux d&#x27;avoir la possibilité d&#x27;élargir le champ d&#x27;une requête afin de trouver d&#x27;autres données potentiellement pertinentes.</p><p>Cet article de blog traite de l&#x27;utilisation d&#x27;Elasticsearch et d&#x27;Azure Open AI pour créer un exemple d&#x27;application qui démontre comment trouver des résultats exacts lors de la recherche de biens immobiliers très spécifiques tout en fournissant des résultats pertinents lorsqu&#x27;une correspondance spécifique n&#x27;est pas disponible. Nous verrons toutes les étapes nécessaires à la création d&#x27;un index Elasticsearch et d&#x27;un modèle de recherche. Ensuite, nous présenterons l&#x27;ensemble du processus de création d&#x27;une application qui utilise Azure OpenAI pour prendre en compte les requêtes des utilisateurs et les transformer en requêtes de modèles de recherche Elasticsearch qui peuvent produire des résultats étonnamment personnalisés.</p><p>Voici une liste de toutes les ressources que nous utiliserons pour créer notre exemple d&#x27;application de recherche immobilière :</p><ul><li>Index et modèle de recherche Elasticsearch</li><li>Azure OpenAI</li><li>API Azure Maps</li><li><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb">Codespaces Jupyter Notebook</a></li><li>Noyau sémantique</li><li>Application C# avec Blazor Frontend</li></ul><h2>Processus d&#x27;interrogation intelligent</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0c4a7c835e06c514f158c00ab1055a7ba719a35f-1600x765.png" style="width: 100%;" /><p>Ce flux de travail combine le LLM, les outils LLM et la recherche pour transformer les requêtes en langage naturel en résultats de recherche structurés et pertinents :</p><ul><li><strong>LLM (Large Language Model)</strong> - Interprète les requêtes complexes des utilisateurs et orchestre les outils pour extraire les intentions de recherche et enrichir le contexte.</li><li><strong>Outils LLM</strong> - Chaque outil LLM est un programme C# que nous avons créé pour cet article. Il existe trois outils :<ul><li><em>Outil d&#x27;extraction de paramètres</em>: il extrait de la requête des attributs clés tels que les chambres, les salles de bain, les caractéristiques et le prix.</li><li><em>Outil GeoCode</em>: convertit les noms de lieux en latitude/longitude pour le filtrage spatial.</li><li><em>Outil de recherche</em>: remplit un modèle de recherche Elasticsearch avec des paramètres de requête et exécute une recherche. <strong>Recherche hybride</strong> - Exécute une recherche hybride (texte intégral + vecteur dense) avec inférence ML intégrée. Cette approche stratifiée garantit à l&#x27;utilisateur final des expériences de recherche plus intelligentes et adaptées au contexte.</li></ul></li></ul><h2>Architecture de l&#x27;application</h2><p>Voici un schéma de l&#x27;architecture du système de l&#x27;application d&#x27;exemple. Nous utiliserons un carnet Jupyter de Codespaces pour interagir avec Elastic Cloud.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2880afee184cd9270c0eb4310e51418e2339784d-936x452.png" style="width: 100%;" /><h2>Produits requis</h2><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><h2>Cloner le repo de l&#x27;application exemple dans Codespaces</h2><p>Commencez par cloner le code de l&#x27;application d&#x27;exemple. Vous pouvez le faire dans <a href="https://github.com/codespaces/">GitHub Codespaces</a>, qui permet de cloner et d&#x27;exécuter des applications. Cliquez sur <strong>New Codespace</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b89cbec491659b6c8a0bb9551ed2629f7a37f9fd-1600x427.png" style="width: 100%;" /><p>Ensuite, sélectionnez le repo <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo">jwilliams-elastic/msbuild-intelligent-query-demo</a> dans la liste déroulante <strong>Repository</strong> et cliquez sur <strong>Create Codespace</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/43ea377554527af9578400f16cd2342bf8fff3a2-1600x1049.png" style="width: 100%;" /><h2>Créer un fichier .env fichier</h2><p>Nous utiliserons un carnet Jupyter Python pour accéder et interagir avec Elastic Cloud en utilisant les valeurs de configuration qui sont stockées dans un fichier de configuration. Le fichier de configuration de l&#x27;ordinateur portable doit avoir le nom de fichier <strong><em>.env.</em></strong> et vous allez le créer maintenant.</p><ol><li>Dans GitHub Codespaces, cliquez sur le bouton <strong>New File </strong>et ajoutez un fichier nommé <strong><em>.env.</em></strong></li><li>Ajoutez le contenu suivant au <strong><em> fichier .env</em></strong> nouvellement créé fichier</li></ol><pre><code>ELASTIC_URL=
ELASTIC_API_KEY=</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/84d4f327948858ba61db0001dd8cf780d42fe0a7-1600x875.gif" style="width: 100%;" /><p>Comme vous pouvez le voir, nous avons quelques valeurs manquantes, <strong>ELASTIC_URL</strong> et <strong>ELASTIC_API_KEY,</strong> qui doivent être ajoutées au <em> fichier .env.</em> fichier. Commençons maintenant par créer un projet Elasticsearch serverless qui servira de backend pour alimenter la fonctionnalité de recherche de notre application d&#x27;exemple.</p><h2>Créer un projet Elastic Serverless</h2><ol><li>Allez sur <a href="http://cloud.elastic.co">cloud.elastic.co</a> et cliquez sur <strong>Create New Serverless project (Créer un nouveau projet sans serveur)</strong>.</li><li>Cliquez sur <strong>Suivant </strong>pour la solution <strong>Elasticsearch</strong></li><li>Sélection <strong>optimisée pour les vecteurs</strong></li><li>Définir le <strong>fournisseur de services en nuage</strong> comme étant <strong>Azure</strong></li><li>Cliquez sur <strong>Créer un projet sans serveur</strong></li><li>Cliquez sur &quot; <strong>Getting Started&quot;</strong> dans le menu de navigation principal et faites défiler vers le bas pour copier les &quot; <strong>Connection Details&quot; (détails de connexion)</strong>.</li><li>Cliquez sur le bouton <strong>Copier </strong>pour copier le <strong>point de terminaison Elasticsearch</strong> à partir des <strong>détails de la connexion.</strong></li><li>Mise à jour du <strong><em> fichier .env</em></strong> pour définir l&#x27;<strong>URL ELASTIC</strong> comme étant le <strong>point de terminaison Elasticsearch</strong>copié.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/77a35e55446d396066b68cfd132d1543a07b81cc-1600x875.gif" style="width: 100%;" /><h2>Créer une clé API Elastic</h2><ol><li>Ouvrez la page Elasticsearch <strong>Getting Started</strong>, cliquez sur <strong>New</strong> dans la section <strong>Add an API Key.</strong></li><li>Saisir un <strong>nom de</strong>clé</li><li>Cliquez sur<strong> Créer une clé API</strong></li><li>Cliquez sur le bouton Copier pour copier la valeur de la clé API.</li><li>De retour dans <strong>Codespaces,</strong> où nous avons le fichier <strong><em>.env</em> </strong>ouvert pour édition, collez la valeur copiée pour définir la<strong> clé ELASTIC_API_KEY</strong></li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/20168cba493d8e2c0d9ae7704eb0ae707df58e4c-1600x875.gif" style="width: 100%;" /><h2>Ouvrez le Codespaces Notebook et installez les dépendances de la bibliothèque.</h2><p>Dans l&#x27;explorateur de fichiers, sélectionnez le fichier <strong><em>VectorDBSetup.ipynb</em></strong> pour ouvrir le carnet de notes. Une fois le Notebook chargé, recherchez la <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L40-L52">section du <strong>Notebook intitulée Installer</strong></a><strong> les bibliothèques. </strong>Cliquez sur le bouton de lecture de la section.</p><p>Si c&#x27;est la première fois que vous exécutez un Notebook dans GitHub Codespaces, vous serez invité à sélectionner un Codespaces Kernel et à configurer l&#x27;environnement Python.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2272f70615dfb9dcbeb91f39b6dd5076213e24a5-1600x875.gif" style="width: 100%;" /><h2>Définir les importations et charger les variables d&#x27;environnement à l&#x27;aide de Codespaces Notebook</h2><p>Passez à la section suivante du Carnet de notes <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L58-L104">intitulée <strong>Définir les importations et charger les variables d&#x27;environnement</strong></a>. Cliquez sur le bouton de lecture de la section.</p><p>Ce code importe les bibliothèques Python utilisées par le Notebook et charge les variables d&#x27;environnement à partir du fichier <em>.env </em>que vous avez créée précédemment.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aa9f9198ff452ac0c4ce33b00f253731dbee22c5-1600x875.gif" style="width: 100%;" /><h2>Créer un point de terminaison d&#x27;inférence Elastic ML à l&#x27;aide de Codespaces Notebook</h2><p>Passez à la <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L112-L157">section suivante du Notebook intitulée <strong>Create ML inference endpoint (Créer un point de terminaison d&#x27;inférence</strong></a> ML). Cliquez sur le bouton de lecture de la section.</p><p>Cela créera un nouveau point de terminaison d&#x27;inférence ML dans le projet Elasticsearch que nous utiliserons pour générer des enchâssements de texte à partir de nos données. Les text embeddings sont des représentations vectorielles du texte qui seront stockées dans Elasticsearch pour alimenter la recherche sémantique.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/97c1afab3e64027ee5ae77f377d56ba406ae1765-1600x875.gif" style="width: 100%;" /><h2>Créer un index Elasticsearch avec Codespaces Notebook</h2><p>Passez à la <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L165-L224">section suivante du <strong>Notebook intitulée Create Elasticsearch index (Créer</strong></a> un index Elasticsearch). Cliquez sur le bouton de lecture de la section.</p><p>Cela créera l&#x27;index Elasticsearch qui stockera nos données d&#x27;exemple et toutes les données vectorielles associées générées via le point de terminaison de l&#x27;inférence ML.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/23a66283ee41239e24fb8455c3cd95641982ca6b-1600x875.gif" style="width: 100%;" /><h2>Créer un modèle de recherche Elasticsearch à l&#x27;aide de Codespaces Notebook</h2><p>Passez à la <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L232-L384">section suivante du carnet <strong>de notes intitulée Modèle de</strong></a> recherche. Cliquez sur le bouton de lecture de la section.</p><p>Cela permet de créer un <a href="https://www.elastic.co/fr/docs/solutions/search/search-templates">modèle de recherche</a>, que notre exemple d&#x27;application utilisera en tant que modèle rempli avec les mots analysés à partir de la requête de recherche de l&#x27;utilisateur. Cela nous permet de configurer et de contrôler à quel point nous sommes spécifiques lorsque nous interrogeons des données dans l&#x27;index Elasticsearch.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4c001a3e4d1cca4cfb5c043fea92c7ccaf9cb64a-1600x875.gif" style="width: 100%;" /><h2>Ingérer des données dans l&#x27;index Elasticsearch à l&#x27;aide de Codespaces Notebook</h2><p>Passez à la section suivante du Carnet de notes <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L392-L450">, intitulée <strong>Acquisition de données sur les biens immobiliers</strong></a>. Cliquez sur le bouton d&#x27;exécution de la section.</p><p>L&#x27;exécution de cette section de code permet de charger en bloc les données de l&#x27;exemple contenues dans le fichier <em>properties.jsonl. </em> Au bout de quelques minutes, vous devriez voir apparaître une confirmation indiquant que le processus s&#x27;est déroulé avec succès. Vous pouvez confirmer que l&#x27;index contient l&#x27;enregistrement attendu en allant dans la section <strong>Gestion de l&#x27;index</strong> dans Elastic Cloud.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e9698c798541ccfc08143a939846597028e3c566-1600x875.gif" style="width: 100%;" /><h2>Créer appsetting.json pour configurer l&#x27;application C#</h2><p>Notre index Elasticsearch étant créé et alimenté en données, nous sommes maintenant prêts à configurer l&#x27;application d&#x27;exemple pour qu&#x27;elle fonctionne avec Elastic et Azure Cloud. L&#x27;application d&#x27;exemple C# utilise un fichier nommé <em>appsettings.json</em> pour stocker et charger ses informations d&#x27;accès telles que les clés API. Vous allez créer le fichier <em>appsettings.json</em> en utilisant l&#x27;éditeur de Codespaces.</p><p>1. Créer <em>appsettings.json</em> dans le dossier <strong>HomeFinderApp </strong></p><p>2. Collez le code suivant dans le fichier <em>appsettings.json</em></p><pre><code>{
 "ElasticSettings": {
   "Url": "",
   "ApiKey": "",
   "IndexName": "properties",
   "TemplateId": "properties-search-template"
 },
 "AzureOpenAISettings": {
   "Endpoint": "",
   "ApiKey": "",
   "DeploymentName": "gpt-4o"
 },
 "AzureMapsSettings": {
   "Url": "https://atlas.microsoft.com/geocode",
   "ApiKey": ""
 },
 "Logging": {
   "LogLevel": {
 	"Default": "Information",
 	"Microsoft.AspNetCore": "Warning"
   }
 },
 "AllowedHosts": "*"
}
</code></pre><p>3. Trouvez les valeurs <strong>Url</strong> et <strong>ApiKey</strong> dans la section <strong>ElasticSettings </strong>. Définissez les mêmes valeurs que celles que vous avez définies dans le fichier <em>.env</em> dans une étape précédente.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/00dad25bacdea2adcbd1e6eca7658867a49b0d8c-1600x875.gif" style="width: 100%;" /><h2>Créer un service Azure OpenAI</h2><p>Notre application d&#x27;exemple va utiliser Azure OpenAI pour analyser la requête de l&#x27;utilisateur de l&#x27;application et ensuite soumettre une demande à Elasticsearch en remplissant le modèle de recherche pour tenter de communiquer de manière flexible ce que l&#x27;utilisateur recherche.</p><ol><li>Ouvrez un nouvel onglet de navigateur et accédez à <a href="https://portal.azure.com/#blade/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/OpenAI">AI Foundry | Azure OpenAI</a> dans le portail Azure. Cliquez sur<strong>+Créer</strong></li><li>Dans le formulaire de création, sélectionnez un <strong>groupe de ressources</strong>.</li><li>Saisir un <strong>nom</strong></li><li>Choisir un <strong>niveau de tarification</strong></li><li>Cliquez sur <strong>Suivant</strong></li><li>Dans l&#x27;onglet <strong>Réseau</strong>, cliquez sur<strong> Suivant</strong></li><li>Dans l&#x27;onglet <strong>Tags</strong>, cliquez sur <strong>Suivant</strong></li><li>Dans l&#x27;onglet <strong>Réviser et soumettre</strong>, cliquez sur <strong>Créer</strong></li><li>Une fois la création terminée, cliquez sur <strong>Aller à la ressource.</strong></li><li>Sélectionnez <strong>Keys and Endpoint</strong> dans le menu de navigation de gauche.</li><li>Copiez le <strong>point de terminaison</strong> et collez-le dans le fichier <em>appsettings.json</em> que vous avez créé dans l&#x27;onglet de votre navigateur où se trouve l&#x27;éditeur Codespaces.</li><li>Retournez ensuite à l&#x27;onglet de votre navigateur contenant la <strong>clé</strong> Azure OpenAI et la page du point de terminaison. Cliquez sur le bouton copier pour la <strong>clé 1</strong>, et collez la valeur copiée dans le fichier <em>appsettings.json</em>, dans l&#x27;onglet de votre navigateur où se trouve l&#x27;éditeur Codespaces ouvert.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6d49197302d110410dca0a53b6ae90237cf2dfd6-1600x875.gif" style="width: 100%;" /><h2>Ajouter le déploiement du modèle gpt-4o au service Azure Open AI</h2><p>Super, nous avons maintenant un service Azure OpenAI qui fonctionne, mais il a encore besoin d&#x27;un déploiement de modèle pour nous donner les capacités LLM dont notre application d&#x27;exemple a besoin. Il existe une multitude de modèles. Déployons <em>gpt-4o</em> puisqu&#x27;il est déjà spécifié dans le fichier <em>appsettings.json</em> que vous avez créé.</p><p></p><ol><li>Accédez à <a href="https://ai.azure.com/resource/playground">Azure AI Foundry</a> et cliquez sur <strong>Créer un déploiement.</strong></li><li>Rechercher <em>gpt-4o</em> et le sélectionner dans les résultats</li><li>Cliquez sur <strong>Confirmer</strong> pour le sélectionner</li><li>Cliquez sur <strong>Déployer</strong> pour déployer le modèle</li></ol><p>Une fois que vous avez déployé avec succès le modèle <em>gpt-4o</em>, vous pouvez sélectionner <strong>Déploiements</strong> dans le menu de navigation de gauche et confirmer que le déploiement <strong><em>gpt-4o</em></strong> est listé avec l&#x27;<strong>état</strong> &quot; <strong>Succeeded&quot;</strong>.<br/></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1b817ab67c05634e9c72777593b4d1a2c6c28191-1600x875.gif" style="width: 100%;" /><h2>Créer un compte Azure Maps</h2><p>Nous voulons que les utilisateurs de notre exemple d&#x27;application puissent rechercher des biens immobiliers dans des zones spécifiques, mais sans avoir à être trop précis. Si quelqu&#x27;un veut chercher une propriété près du marché local, Azure Maps est un service que l&#x27;OpenAI LLM peut utiliser pour obtenir les coordonnées de latitude et de longitude du marché. Les coordonnées peuvent ensuite être incluses dans les requêtes basées sur le modèle de recherche envoyé à Elasticsearch pour les requêtes des utilisateurs qui incluent des emplacements spécifiques et des considérations de géo-distance.</p><ol><li>Cliquez sur <strong>Créer</strong> dans les <a href="https://portal.azure.com/#browse/Microsoft.Maps%2Faccounts">comptes Azure Maps</a></li><li>Sélectionner un <strong>groupe de ressources</strong></li><li>Saisir un <strong>nom</strong></li><li>Accepter la licence et la déclaration de confidentialité</li><li>Cliquez sur <strong>Réviser et créer</strong></li><li>Cliquez sur <strong>Créer</strong></li><li>Une fois la création du compte terminée, cliquez sur <strong>Aller à la ressource.</strong></li><li>Cliquez sur <strong>Authentification</strong> dans le menu de navigation de gauche</li><li>Copier la valeur de la <strong>clé primaire</strong> et la coller comme valeur de l&#x27;<strong>ApiKey</strong> dans la section <strong>AzureMapsSettings</strong> du fichier <em>appsettings.json</em>, de nouveau dans l&#x27;onglet de votre navigateur contenant l&#x27;éditeur Codespaces.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/90fadd48e366682e2bad91e32988f93c6354e126-1600x875.gif" style="width: 100%;" /><h2>Essayez l&#x27;application d&#x27;exemple</h2><p>Passons maintenant à la partie la plus amusante. Il est temps d&#x27;exécuter l&#x27;application d&#x27;exemple. Nous avons mis en place tous les détails de configuration ainsi que les ressources Elastic Cloud et Azure Cloud dont nous avons besoin pour faire fonctionner l&#x27;application.</p><p>1. Ouvrez une fenêtre Terminal dans l&#x27;éditeur Codespaces.</p><p>2. Utilisez la commande suivante pour modifier le répertoire actif afin qu&#x27;il devienne le dossier de l&#x27;application d&#x27;exemple.<br/></p><pre><code>cd HomeFinderApp</code></pre><p>3. Utilisez la commande <em>dotnet</em> suivante pour exécuter l&#x27;application.</p><pre><code>dotnet run</code></pre><p>4. Cliquez sur le bouton <strong>Ouvrir dans le navigateur </strong>lorsqu&#x27;il apparaît.</p><p>5. Testez la recherche par défaut, puis testez vos propres recherches personnalisées. Si vous souhaitez obtenir plus de détails sur ce qui est exécuté en amont pour générer les résultats de la recherche, vous pouvez cliquer sur le lien <strong>Afficher </strong>situé à côté de <strong>Invocations d&#x27;outils.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/821fe7b9446de5ed646d938cc9484a7ddad21030-1600x875.gif" style="width: 100%;" /><p><strong>Bonus : </strong>Si vous voulez vraiment tester GPT-4o, essayez la recherche suivante : <em>Je cherche une propriété près de Disney World Fl avec plus de 30 chambres et plus de 20 salles de bain, une piscine, un garage et près de la plage pour moins de 200K. </em>Cette requête renverra des résultats après plusieurs invocations de l&#x27;outil de recherche.</p><h2>Elastic est votre solution pour Search AI</h2><p>L&#x27;application en cours est un exemple de recherche guidée Gen AI LLM utilisant Elasticsearch via des modèles de recherche comme source de données de base. N&#x27;hésitez pas à expérimenter et à personnaliser l&#x27;application d&#x27;exemple pour créer une expérience de recherche à la fois précise et flexible, afin d&#x27;aider vos utilisateurs à trouver ce qu&#x27;ils recherchent.</p><p>Merci de votre lecture. Essayez <a href="https://cloud.elastic.co/registration">Elastic Cloud</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[L'état actuel du MCP (Model Context Protocol)]]></title>
            <description><![CDATA[Découvrez MCP, les mises à jour du projet, les fonctionnalités, les défis de sécurité, les cas d'utilisation émergents, et comment bricoler avec le serveur Elasticsearch MCP d'Elastic.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/mcp-current-state</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/mcp-current-state</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/02b8c8672ffa129e0ed91a92d6cab612a01d27f2-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>J&#x27;ai récemment assisté au <a href="https://mcpdevsummit.ai/">sommet des développeurs MCP</a> à San Francisco et il est clair que le protocole MCP (Model Context Protocol) devient rapidement un élément fondamental pour les agents d&#x27;intelligence artificielle et les applications d&#x27;intelligence artificielle riches en contexte. Chez Elastic, nous nous orientons dans cette direction en exposant les serveurs MCP directement à partir d&#x27;<a href="https://www.elastic.co/fr/elasticsearch/agent-builder">Agent Builder</a>, faisant d&#x27;Elasticsearch un contexte de premier ordre et un fournisseur d&#x27;outils pour tout agent compatible MCP. Dans ce billet, je vais passer en revue les principales mises à jour de l&#x27;événement, les cas d&#x27;utilisation émergents, ce qui se profile à l&#x27;horizon pour MCP, et comment vous pouvez utiliser Agent Builder pour rendre Elasticsearch disponible aux agents via MCP.</p><h2>Qu&#x27;est-ce que le protocole de contexte de modèle (MCP) ?</h2><p>Pour ceux qui ne connaissent pas, <a href="https://modelcontextprotocol.io/introduction">Model Context Protocol</a> est une norme ouverte qui offre un moyen structuré et bidirectionnel de connecter les modèles d&#x27;IA à diverses sources de données et à divers outils, ce qui leur permet de générer des réponses plus pertinentes et mieux informées. Il est communément appelé &quot;<a href="https://modelcontextprotocol.io/introduction">port USB-C pour les applications d&#x27;intelligence artificielle</a>&quot;.</p><p>Voici un schéma architectural qui met en évidence son caractère bidirectionnel :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5eba1e59514eb58a5220bb92bb49e6328ee83cd7-674x466.png" style="width: 100%;" /><p>Il s&#x27;agit d&#x27;un changement important pour les praticiens de l&#x27;IA, car l&#x27;un des principaux défis de la mise à l&#x27;échelle des applications d&#x27;IA est de devoir créer des intégrations personnalisées pour chaque nouvelle source de données. MCP offre une architecture durable et réutilisable pour la gestion et la fourniture de contexte aux modèles. Il est indépendant des modèles et des serveurs et est entièrement open source.</p><p>MCP est la dernière itération d&#x27;une lignée de spécifications d&#x27;API visant à normaliser l&#x27;intégration entre les applications. Dans le passé, nous avions OpenAPI pour les services RESTful, GraphQL pour l&#x27;interrogation des données et gRPC pour la communication des microservices. MCP ne partage pas seulement la rigueur structurée de ces anciennes spécifications, mais l&#x27;intègre également dans un cadre d&#x27;IA générative, ce qui facilite l&#x27;intégration des agents dans différents systèmes sans avoir recours à des connecteurs personnalisés. À bien des égards, MCP vise à faire pour les agents d&#x27;intelligence artificielle ce que HTTP a fait pour le web. Tout comme HTTP a normalisé la communication entre les navigateurs et les sites web, MCP cherche à normaliser la façon dont les agents d&#x27;intelligence artificielle interagissent avec le monde des données qui les entoure.</p><h2>MCP par rapport à d&#x27;autres protocoles d&#x27;agents</h2><p>Le paysage des protocoles d&#x27;agents se développe rapidement, avec plus d&#x27;une douzaine de normes émergentes en concurrence pour définir la manière dont les agents interagissent. <a href="https://x.com/seldo">Laurie Voss</a> de LlamaIndex décrit comment la plupart peuvent être catégorisés en 2 types : les protocoles inter-agents qui se concentrent sur les agents qui parlent entre eux et les protocoles orientés vers le contexte comme MCP qui se concentrent sur la fourniture d&#x27;un contexte structuré aux LLMs.</p><p>D&#x27;autres protocoles populaires comme <a href="https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/">A2A</a> (Agent to Agent) de Google, <a href="https://agentcommunicationprotocol.dev/introduction/welcome">ACP</a> (Agent Communication Protocol) de Cisco et IBM, et <a href="https://agoraprotocol.org/">Agora</a>, visent à permettre les négociations entre agents, la création de coalitions et même des systèmes d&#x27;identité décentralisés. MCP adopte une approche un peu plus pragmatique en ce sens qu&#x27;il se concentre sur la manière dont les agents accèdent aux outils et aux données et pas nécessairement sur la manière dont ils communiquent entre eux (bien que MCP puisse également permettre cela à l&#x27;avenir de différentes manières).</p><p>Actuellement, ce qui distingue MCP, c&#x27;est sa traction et son élan. Comme React dans les premiers jours des frameworks frontaux, MCP a commencé avec un problème de niche et est maintenant l&#x27;un des protocoles d&#x27;agent les plus adoptés et les plus extensibles dans la pratique.</p><h2>Récapitulation du sommet : Évolution des priorités pour le MCP</h2><p>Le sommet a accueilli des intervenants de Anthropic, Okta, OpenAI, AWS, GitHub et bien d&#x27;autres. Les discussions ont porté sur l&#x27;amélioration des protocoles de base et sur leur mise en œuvre dans le monde réel, et ont mis en évidence les priorités immédiates et à long terme. Ces exposés ont montré que l&#x27;on s&#x27;éloignait de l&#x27;expérimentation précoce et de l&#x27;appel à de simples outils pour passer à la construction de systèmes d&#x27;IA fiables, évolutifs et modulaires en utilisant le MCP comme base.</p><p>Plusieurs orateurs ont évoqué un avenir où le MCP est plus qu&#x27;un simple protocole, il peut devenir le fondement d&#x27;un web orienté vers l&#x27;intelligence artificielle. Tout comme JavaScript permet aux utilisateurs de cliquer et d&#x27;interagir avec les pages web, MCP pourrait permettre aux agents d&#x27;effectuer ces mêmes actions en notre nom. Par exemple, dans le domaine du commerce électronique, au lieu de naviguer manuellement sur un site web pour faire des achats, les utilisateurs pourraient simplement demander à un agent de se connecter, de trouver un produit spécifique, de l&#x27;ajouter à leur panier et de passer à la caisse.</p><p>Il ne s&#x27;agit pas non plus d&#x27;une pure spéculation ou d&#x27;un battage médiatique : PayPal a présenté sa nouvelle boîte à outils pour agents et son serveur MCP lors du sommet, qui permet exactement cette expérience de commerce agentique. Grâce à MCP qui fournit un accès sécurisé et fiable aux outils et aux sources de données, les agents ne se contenteront pas de lire le web, ils pourront agir en conséquence. Aujourd&#x27;hui, MCP est déjà une norme puissante qui a le vent en poupe et, à terme, elle pourrait devenir la norme des interactions utilisateur améliorées par l&#x27;IA sur l&#x27;ensemble du web.</p><h2>Mise à jour du projet MCP : Transport, élicitation et outils structurés</h2><p><a href="https://x.com/JeromeSwannack">Jerome Swannack</a>, l&#x27;un des principaux contributeurs à MCP, a fait part de quelques mises à jour de la spécification du protocole au cours des six derniers mois. Les principaux objectifs de ces changements sont les suivants</p><ol><li>Pour activer le MCP à distance avec l&#x27;ajout du HTTP en continu</li><li>Permettre des modèles d&#x27;interaction entre agents plus riches grâce à l&#x27;ajout de schémas d&#x27;élicitation et de sortie d&#x27;outils.</li></ol><p>MCP étant un logiciel libre, les développeurs peuvent déjà mettre en œuvre des modifications telles que le protocole HTTP fluide. Les schémas d&#x27;élicitation et de sortie de l&#x27;outil ne sont pas encore publiés ; ils sont à l&#x27;état de projet et peuvent évoluer.</p><p><strong>Streamable HTTP </strong><a href="https://modelcontextprotocol.io/specification/2025-03-26/basic/transports">(publié le 26 mars 2025</a>)<strong>:</strong> Une mise à jour technique importante a été l&#x27;introduction du protocole HTTP en flux continu en tant que nouveau mécanisme de transport. Il remplace les événements envoyés par le serveur (SSE) par un modèle bidirectionnel plus évolutif qui prend en charge le codage de transfert par morceaux et la transmission progressive de messages sur une seule connexion HTTP. Cela vous permet de déployer des serveurs MCP sur une infrastructure cloud comme AWS Lambda et de prendre en charge les contraintes du réseau d&#x27;entreprise sans connexions à longue durée de vie ni nécessité de polling.</p><p><strong>Elicitation </strong><a href="https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation">(publié le 18 juin 2025</a>)<strong>:</strong> L&#x27;élicitation permet aux serveurs de définir un schéma sur la façon dont ils veulent que le contexte soit structuré à partir d&#x27;un client. Essentiellement, le serveur peut décrire ce dont il a besoin et le type d&#x27;entrée qu&#x27;il attend. Cela a plusieurs implications : Les constructeurs de serveurs peuvent créer des interactions agentiques plus complexes. Les concepteurs de clients peuvent mettre en œuvre des interfaces utilisateur dynamiques qui s&#x27;adaptent à ces schémas. Cependant, l&#x27;élicitation ne doit pas être utilisée pour extraire des informations sensibles ou personnellement identifiables des utilisateurs. Les développeurs doivent suivre les <a href="https://modelcontextprotocol.io/specification/draft/client/elicitation#security-considerations">meilleures pratiques</a> pour s&#x27;assurer que les messages d&#x27;élicitation restent sûrs et appropriés, en particulier au fur et à mesure que le MCP évolue. Ceci est lié à des problèmes de sécurité plus larges que nous aborderons plus loin dans ce billet.</p><p><strong>Schémas de sortie d&#x27;outil </strong><a href="https://modelcontextprotocol.io/specification/draft/server/tools#output-schema">(publié le 18 juin 2025</a>)<strong>: </strong>Ce concept permet au client et au LLM de connaître à l&#x27;avance les formes de sortie de l&#x27;outil. Les schémas de sortie des outils permettent aux développeurs de décrire les résultats attendus d&#x27;un outil. Ces schémas répondent à l&#x27;une des principales limites de l&#x27;appel direct d&#x27;outils, à savoir l&#x27;utilisation inefficace de la fenêtre contextuelle. La fenêtre contextuelle est considérée comme l&#x27;une des ressources les plus importantes lorsque vous travaillez avec des LLM. Lorsque vous appelez directement un outil, celui-ci renvoie un contenu brut qui est entièrement transféré dans le contexte du LLM. Les schémas de sortie des outils peuvent vous aider à mieux utiliser vos jetons et la fenêtre contextuelle en permettant au serveur MCP de fournir des données structurées. Voici quelques <a href="https://modelcontextprotocol.io/specification/draft/server/tools#security-considerations">bonnes pratiques</a> concernant les outils en général.</p><p>Ensemble, ces nouvelles mises à jour et les ajouts futurs aideront le MCP à devenir un protocole d&#x27;agent plus modulaire, typé et prêt pour la production.</p><h2>Caractéristiques de puissance sous-utilisées : Échantillonnage et racines</h2><p>Bien qu&#x27;ils ne soient pas nouveaux dans la spécification MCP, l&#x27;échantillonnage et les racines ont été mis en évidence lors de la présentation. Ces deux primitives sont actuellement négligées et sous-explorées, mais elles peuvent contribuer de manière significative à des interactions plus riches et plus sûres entre les agents.</p><p><strong>Échantillonnage - Les serveurs peuvent demander des compléments au client : </strong>L&#x27;<a href="https://modelcontextprotocol.io/docs/concepts/sampling">échantillonnage</a> permet aux serveurs MCP de demander des compléments au LLM côté client. Cela ajoute à la nature bidirectionnelle du protocole, où le serveur ne se contente pas de répondre aux demandes ; il peut inciter et demander au modèle du client de générer une réponse. Cela permet au client de garder un contrôle total sur le coût, la sécurité et le modèle utilisé par le serveur MCP. Ainsi, dans le cas de l&#x27;utilisation d&#x27;un serveur MCP externe avec un modèle préconfiguré, vous n&#x27;aurez pas besoin de fournir vos propres clés API ou de configurer votre propre abonnement à ce modèle, car le serveur peut simplement demander le modèle déjà connecté au client. Cela permet d&#x27;obtenir des comportements d&#x27;agents plus complexes et plus interactifs.</p><p><strong>Racines - Accès limité aux ressources : </strong>Les<a href="https://modelcontextprotocol.io/docs/concepts/roots">racines</a> ont été conçues pour permettre aux clients d&#x27;informer les serveurs des ressources et des espaces de travail pertinents sur lesquels ils doivent se concentrer. Il s&#x27;agit d&#x27;un outil puissant pour définir le champ d&#x27;action des serveurs. Il est important de noter que les racines sont &quot;<a href="https://modelcontextprotocol.io/docs/concepts/roots#how-roots-work">informatives et non strictement exécutoires</a>&quot;, ce qui signifie qu&#x27;elles ne définissent pas de droits ou de permissions pour les serveurs ou les agents MCP. En d&#x27;autres termes, vous ne pouvez pas vous fier uniquement aux racines pour empêcher un serveur ou un agent d&#x27;exécuter certains outils ou d&#x27;effectuer des actions d&#x27;écriture. En ce qui concerne les racines, les autorisations doivent toujours être gérées du côté du client, avec des mécanismes d&#x27;approbation par l&#x27;utilisateur. Par ailleurs, les développeurs doivent toujours veiller à utiliser des serveurs conçus pour respecter les limites fixées par les racines et utiliser les <a href="https://modelcontextprotocol.io/docs/concepts/roots#best-practices">meilleures pratiques.</a></p><h2>Authentification des agents : OAuth 2.1 et métadonnées protégées</h2><p>Cette section se concentre sur OAuth 2.1, la dernière itération d&#x27;OAuth 2.0, qui supprime les flux non sécurisés et consolide les meilleures pratiques.</p><p>La prise en charge d&#x27;OAuth était un sujet très attendu, d&#x27;autant plus que la sécurité et l&#x27;évolutivité sont considérées comme les principaux obstacles qui empêchent MCP de devenir la norme pour la connexion des agents aux outils. <a href="https://x.com/aaronpk">Aaron Parecki</a> (éditeur d&#x27;OAuth 2.1 et expert en normes d&#x27;identité chez Okta) a expliqué comment MCP peut adopter un flux OAuth propre et évolutif qui décharge les développeurs de serveurs de la majeure partie de la complexité. La spécification d&#x27;autorisation officielle OAuth 2.1 a été récemment publiée dans la dernière révision du protocole le <a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization">18 juin 2025</a>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2ea159116fe5e03ff800f077adf16d6ca9f1c1d1-1594x1280.png" style="width: 100%;" /><p>Dans cette implémentation, les responsabilités OAuth peuvent être réparties entre le client MCP et le serveur. La majeure partie du flux d&#x27;authentification est initiée et gérée par le client MCP, le serveur n&#x27;intervenant qu&#x27;à la fin pour recevoir et vérifier le jeton sécurisé. Cette scission permet de résoudre un problème critique de mise à l&#x27;échelle, à savoir comment s&#x27;authentifier à travers de nombreux outils sans exiger des développeurs qu&#x27;ils configurent chaque connexion, et garantit que les développeurs de serveurs MCP n&#x27;ont pas à devenir des experts OAuth.</p><p>Deux points saillants de l&#x27;exposé :</p><ol><li><a href="https://datatracker.ietf.org/doc/rfc9728/"><strong>Métadonnées des ressources protégées</strong></a>: Les serveurs MCP peuvent publier un fichier JSON décrivant leur objectif, leurs points d&#x27;accès et leurs méthodes d&#x27;authentification. Cela permet aux clients de démarrer des flux OAuth avec seulement l&#x27;URL du serveur, ce qui simplifie le processus de connexion. En savoir plus : <a href="https://aaronparecki.com/2025/04/03/15/oauth-for-model-context-protocol">Corrigeons OAuth dans MCP</a></li><li><a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13"><strong>Prise en charge des IDP et du SSO</strong></a>: les entreprises peuvent intégrer des fournisseurs d&#x27;identité pour gérer l&#x27;accès de manière centralisée. C&#x27;est une victoire pour l&#x27;expérience de l&#x27;utilisateur et la sécurité. Les utilisateurs n&#x27;auraient pas besoin de cliquer sur dix écrans de consentement différents et les équipes de sécurité pourraient observer chaque connexion.</li></ol><p>En transférant la logique OAuth au client et en s&#x27;appuyant sur les métadonnées des serveurs, l&#x27;écosystème MCP évite un goulot d&#x27;étranglement majeur. Le MCP s&#x27;aligne ainsi plus étroitement sur la manière dont les API modernes sont sécurisées dans les environnements de production d&#x27;aujourd&#x27;hui.</p><p>Lecture complémentaire : <a href="https://aaronparecki.com/oauth-2-simplified/">OAuth 2 simplifié</a>.</p><h2>Défis de sécurité dans un écosystème composable</h2><p>Les nouveaux développements s&#x27;accompagnent également de nouvelles surfaces d&#x27;attaque. Arjun Sambamoorthy, de Cisco, énumère les principales menaces qui pèsent sur le paysage MCP :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Profils</td><td style="border: 1px solid #ccc; padding: 4px;">Description</td><td style="border: 1px solid #ccc; padding: 4px;">Remédiation & meilleures pratiques</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Injection rapide & Empoisonnement des outils</td><td style="border: 1px solid #ccc; padding: 4px;">Un moyen d'injecter une invite malveillante dans le contexte du système LLM ou dans la description de l'outil, amenant le LLM à effectuer des actions involontaires telles que la lecture de fichiers ou la fuite de données.</td><td style="border: 1px solid #ccc; padding: 4px;">Utilisez des outils tels que MCP Scan pour vérifier les métadonnées des outils. Validez les descriptions et les paramètres avant de les inclure dans les invites. Enfin, envisagez de mettre en place des approbations d'utilisateurs pour les outils à haut risque. Pour plus de détails, voir le guide OWASP Prompt Injection dans la liste de lectures complémentaires sous le tableau.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Attaques par échantillonnage</td><td style="border: 1px solid #ccc; padding: 4px;">Dans le contexte de MCP, l'échantillonnage ouvre la porte au serveur MCP pour effectuer des attaques d'injection rapide sur le LLM.</td><td style="border: 1px solid #ccc; padding: 4px;">Désactiver l'échantillonnage pour les serveurs non fiables et envisager d'ajouter des approbations humaines en boucle pour les demandes d'échantillonnage.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Serveurs MCP malveillants</td><td style="border: 1px solid #ccc; padding: 4px;">Dans les collections actuelles de serveurs MCP, il est difficile de contrôler chacun d'entre eux pour garantir la sécurité. Des serveurs malveillants peuvent discrètement collecter vos données et les exposer à des acteurs malveillants.</td><td style="border: 1px solid #ccc; padding: 4px;">Ne vous connectez qu'à des serveurs MCP provenant de registres de confiance ou de listes internes. Exécuter des serveurs tiers dans des conteneurs avec sandboxing.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Outils d'installation MCP malveillants</td><td style="border: 1px solid #ccc; padding: 4px;">Les installateurs de ligne de commande et les scripts sont pratiques pour mettre en œuvre rapidement des serveurs ou des outils MCP, mais vous risquez d'installer du code non vérifié et compromis.</td><td style="border: 1px solid #ccc; padding: 4px;">Installer dans des environnements "sandbox" et valider les signatures des paquets. Ne jamais effectuer de mise à jour automatique à partir de sources non vérifiées.</td>
                    </tr>
              </tbody>
            </table>
          <p>Pour lutter contre ce problème, Arjun suggère d&#x27;utiliser un registre MCP de confiance pour gérer toutes les vérifications (un sujet qui était au cœur de l&#x27;actualité - pour plus de détails, voir les deux premiers éléments de la liste de lecture ci-dessous), ainsi que d&#x27;utiliser cette <a href="https://github.com/slowmist/MCP-Security-Checklist">liste de contrôle de la sécurité</a>.</p><p>Lecture complémentaire :</p><ul><li><a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices">Meilleures pratiques officielles de sécurité MCP</a></li><li><a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/">Top 10 des candidatures au LLM de l&#x27;OWASP</a></li><li><a href="https://hiddenlayer.com/innovation-hub/">Recherche sur les menaces HiddenLayer</a></li><li><a href="https://github.com/invariantlabs-ai/mcp-scan">Scan MCP</a></li><li><a href="https://genai.owasp.org/llmrisk/llm01-prompt-injection/">Guide OWASP sur l&#x27;injection d&#x27;invites</a></li></ul><h2>Prochaines étapes : Registres, gouvernance et écosystème</h2><p>Un registre centralisé des MCP est en cours d&#x27;élaboration et a été l&#x27;un des sujets les plus discutés lors du sommet. L&#x27;écosystème actuel des serveurs souffre de fragmentation, d&#x27;un manque de confiance et d&#x27;accessibilité. Il est difficile pour les développeurs de trouver des serveurs MCP, de vérifier ce qu&#x27;ils font et de les installer en toute sécurité, en particulier dans un écosystème décentralisé où les métadonnées peuvent être incomplètes ou usurpées.</p><p>Un registre centralisé répond directement à ces problèmes en agissant comme une source de vérité fiable, en améliorant la découvrabilité, en garantissant l&#x27;intégrité des métadonnées du serveur et en réduisant le risque d&#x27;installation d&#x27;outils malveillants.</p><p>Les objectifs du registre MCP sont les suivants :</p><ul><li>Offrir une source unique de vérité pour les métadonnées des serveurs (ce que fait un serveur, comment s&#x27;authentifier, l&#x27;installer et l&#x27;appeler)</li><li>Se débarrasser des registres tiers incomplets et de la fragmentation, de sorte que lorsqu&#x27;un serveur veut être enregistré, il n&#x27;a pas à mettre à jour tous les autres registres sur l&#x27;internet.</li><li>Fournir un flux d&#x27;enregistrement de serveur qui comprend un outil CLI et un fichier server.json qui contient les métadonnées mentionnées précédemment.</li></ul><p>L&#x27;espoir le plus large est qu&#x27;un registre fiable aidera à développer l&#x27;écosystème en toute sécurité, en permettant aux développeurs de créer et de partager de nouveaux outils en toute confiance.</p><p>La gouvernance est une autre question prioritaire pour Anthropic. Ils ont clairement indiqué que le projet MCP devait rester ouvert et dirigé par la communauté, mais la mise à l&#x27;échelle de ce modèle de gouvernance est encore un travail en cours. Ils recherchent actuellement de l&#x27;aide dans ce domaine et demandent à toute personne ayant de l&#x27;expérience en matière de gouvernance dans les protocoles à source ouverte de les contacter. Cela nous amène à l&#x27;autre sujet que je voulais aborder. Tout au long de l&#x27;événement, les intervenants ont insisté sur le fait que l&#x27;écosystème ne peut se développer qu&#x27;avec les contributions des développeurs. Un effort concentré est nécessaire pour faire de MCP la nouvelle norme web et se démarquer des autres protocoles d&#x27;agents populaires.</p><h2>MCP dans le monde réel : Études de cas et démonstrations</h2><p>Plusieurs organisations ont expliqué comment le MCP est déjà utilisé dans des applications pratiques :</p><ul><li><strong>PayPal - Serveur MCP pour le commerce agentique : </strong>PayPal a présenté son nouvel <a href="https://github.com/paypal/agent-toolkit/">agent-toolkit</a> et son serveur MCP, qui peuvent changer fondamentalement l&#x27;expérience d&#x27;achat d&#x27;un utilisateur. Au lieu de parcourir les médias sociaux pour trouver des articles, comparer les prix et passer à la caisse, les utilisateurs peuvent discuter avec un agent qui se connecte au serveur MCP de PayPal pour gérer toutes ces actions.<br/></li><li><strong>EpicAI.pro - Jarvis :</strong> Les développements de MCP nous rapprochent de plus en plus d&#x27;un véritable assistant de type Jarvis. Pour ceux qui ne connaissent pas les films d&#x27;Iron Man, Jarvis est un assistant IA qui utilise le langage naturel, répond aux entrées multimodales, n&#x27;a aucune latence lorsqu&#x27;il répond, est proactif en anticipant les besoins de l&#x27;utilisateur, gère automatiquement les intégrations et peut passer d&#x27;un appareil à l&#x27;autre et d&#x27;un lieu à l&#x27;autre en fonction du contexte. Si nous imaginons Jarvis comme un assistant robotique physique, MCP lui donne des &quot;mains&quot; ou la capacité de gérer des tâches complexes.<br/></li><li><strong>Postman - <a href="https://www.postman.com/explore/mcp-generator">Générateur de serveur MCP</a>: </strong>Fournit une expérience de panier pour les demandes d&#x27;API où vous pouvez choisir différentes demandes d&#x27;API, les mettre dans un panier et télécharger l&#x27;ensemble du panier en tant que serveur MCP.<br/></li><li>Bloomberg <strong>- </strong>Bloomberg a résolu un goulot d&#x27;étranglement important dans le développement de la GenAI en entreprise. Avec près de 10 000 ingénieurs, la société avait besoin d&#x27;un moyen standardisé pour intégrer les outils et les agents au sein des équipes. Avec MCP, ils ont transformé leurs outils internes en composants modulaires à distance que les agents peuvent facilement appeler sur une interface unifiée. Cela a permis à leurs ingénieurs de fournir des outils à l&#x27;ensemble de l&#x27;organisation, tandis que les équipes d&#x27;intelligence artificielle se sont concentrées sur la création d&#x27;agents plutôt que sur des intégrations personnalisées. Bloomberg prend désormais en charge des flux de travail d&#x27;agents évolutifs et sécurisés qui débloquent une interopérabilité totale avec l&#x27;écosystème MCP. Bloomberg n&#x27;a lié aucune ressource publique, mais c&#x27;est ce qu&#x27;ils ont présenté en public lors du sommet.<br/></li><li><strong>Block - </strong>Block utilise MCP pour alimenter <a href="https://github.com/block/goose?tab=readme-ov-file">Goose</a>, un agent d&#x27;intelligence artificielle interne qui permet aux employés d&#x27;automatiser des tâches dans les domaines de l&#x27;ingénierie, des ventes, du marketing, etc. Ils ont construit plus de 60 serveurs MCP pour des outils tels que Git, Snowflake, Jira et Google Workspace afin de permettre une interaction en langage naturel avec les systèmes qu&#x27;ils utilisent tous les jours. Les employés de Block utilisent désormais Goose pour interroger les données, détecter les fraudes, gérer les incidents, naviguer dans les processus internes et bien plus encore, le tout sans avoir à écrire de code. MCP a aidé Block à adopter l&#x27;IA dans de nombreuses fonctions en seulement 2 mois.<br/></li><li><strong>AWS - <a href="https://github.com/awslabs/mcp">Serveurs MCP AWS</a>: </strong>AWS a présenté un serveur MCP amusant sur le thème de Donjons et Dragons qui simule un lancer de dés, suit les lancers passés et renvoie les résultats à l&#x27;aide de HTTP en continu. Cet exemple léger a mis en évidence la facilité avec laquelle il est possible de construire et de déployer des serveurs MCP à l&#x27;aide d&#x27;outils et d&#x27;infrastructures AWS comme Lambda et Fargate. Ils ont également présenté <a href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">Strands SDK</a>, un kit d&#x27;outils open-source pour la création d&#x27;agents multimodaux qui interagissent avec les serveurs MCP.</li></ul><h2>Support MCP dans Elastic Agent Builder</h2><p>Vous pouvez commencer à expérimenter MCP dès aujourd&#x27;hui en utilisant <a href="https://www.elastic.co/fr/search-labs/blog/elastic-ai-agent-builder-context-engineering-introduction">Elastic Agent Builder,</a> qui est le moyen le plus simple de créer des agents directement à partir de vos données. Agent Builder vous permet d&#x27;exposer des outils basés sur Elasticsearch à des agents compatibles avec MCP et il est déjà livré avec quelques outils intégrés puissants, notamment :</p><ul><li><code>platform.core.search</code> - Exécute des recherches à l&#x27;aide du DSL Elasticsearch Query complet.</li><li><code>platform.core.list_indices</code> - Liste de tous les index disponibles dans Elasticsearch (aide les agents à découvrir les données existantes)</li><li><code>platform.core.get_index_mapping</code> - Récupère les correspondances de champs pour un index spécifique (aide les agents à comprendre la forme et les types de vos données).</li><li><code>platform.core.get_document_by_id</code> - Recherche d&#x27;un document spécifique par son numéro d&#x27;identification (pour une recherche précise)</li></ul><p>Grâce à ces outils, vous pouvez doter votre agent d&#x27;une fonction de recherche et de pertinence au niveau de l&#x27;entreprise, ce qui est essentiel pour créer des agents d&#x27;intelligence artificielle fiables.</p><p>Ce qui rend Agent Builder encore plus puissant, c&#x27;est la possibilité de définir et d&#x27;exposer vos propres outils personnalisés, adaptés aux besoins de votre application. Ceci est particulièrement utile pour les flux de travail basés sur l&#x27;opinion ou répétables lorsque vous souhaitez que l&#x27;agent effectue un type de recherche spécifique sur un index spécifique sans avoir à redécouvrir cette logique à chaque fois. Au lieu de dépenser des jetons en planification et en raisonnement pour arriver à la même conclusion, vous pouvez encoder cette intention directement dans un outil, ce qui rend vos agents plus rapides, plus fiables et plus rentables.</p><p>Dans l&#x27;interface utilisateur de l&#x27;Agent Builder, voici un exemple de définition d&#x27;outil personnalisé qui utilise ES|QL :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1f6405a374b707e8e6fa36b9e21db5f3c7cd127-1376x864.png" style="width: 100%;" /><p>Une fois que vous avez défini vos outils personnalisés, vous pouvez les exposer (ainsi que les outils natifs intégrés) à l&#x27;aide de MCP en cliquant sur la liste déroulante <code>Manage MCP</code> et en copiant l&#x27;URL du serveur MCP.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9f39588525ca2643475de557ea54a6bcf5c150f6-1282x616.png" style="width: 100%;" /><p>Vous pouvez maintenant importer ce point de terminaison MCP dans n&#x27;importe quel client qui consomme MCP pour le connecter à Agent Builder et lui donner accès à tous les outils disponibles. Pour plus d&#x27;informations, lisez cette introduction à <a href="https://www.elastic.co/fr/search-labs/blog/elastic-ai-agent-builder-context-engineering-introduction">Agent Builder</a>.</p><h2>Conclusion</h2><p>Le MCP Dev Summit a clairement montré que le MCP façonne la manière dont ces agents d&#x27;intelligence artificielle interagissent entre eux et avec le monde des données qui les entoure. Qu&#x27;il s&#x27;agisse de connecter un agent aux données de l&#x27;entreprise ou de concevoir des agents entièrement autonomes, MCP offre une méthode d&#x27;intégration normalisée et composable qui devient rapidement utile à grande échelle. Des protocoles de transport et des modèles de sécurité aux registres et à la gouvernance, l&#x27;écosystème MCP mûrit rapidement. Le programme MCP restera ouvert et axé sur la communauté, de sorte que les développeurs d&#x27;aujourd&#x27;hui ont la possibilité de façonner son évolution.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[La recherche hybride revisitée : introduction de la recherche linéaire dans Elasticsearch !]]></title>
            <description><![CDATA[Découvrez comment le récupérateur linéaire améliore la recherche hybride en exploitant les scores pondérés et la normalisation MinMax pour des classements plus précis et plus cohérents, et apprenez à l'utiliser.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/linear-retriever-hybrid-search</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/linear-retriever-hybrid-search</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7a07e27e3095463ff93b4cb7f8a0cf3b8e44eab0-1777x1000.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans notre <a href="https://www.elastic.co/fr/search-labs/blog/elasticsearch-retrievers-ga-8.16.0">précédent article</a> de blog, nous avons présenté le cadre des récupérateurs remanié de A à Z, qui permet de créer des pipelines de classement complexes. Nous avons également étudié la manière dont le récupérateur Reciprocal Rank Fusion (RRF) permet une recherche hybride en fusionnant les résultats de différentes requêtes. Bien que la méthode RRF soit facile à mettre en œuvre, elle présente une limitation notable : elle se concentre uniquement sur les rangs relatifs, sans tenir compte des scores réels. Cela rend le réglage fin et l&#x27;optimisation difficiles.</p><h2>Rencontrez le retriever linéaire !</h2><p>Dans ce billet, nous vous présentons le <a href="https://www.elastic.co/fr/docs/solutions/search/retrievers-overview#retrievers-overview-types"><code>linear</code></a> <a href="https://www.elastic.co/fr/docs/solutions/search/retrievers-overview#retrievers-overview-types">retriever</a>, notre dernier ajout pour soutenir la recherche hybride ! Contrairement à <code>rrf</code>, le récupérateur <code>linear</code> calcule une somme pondérée de toutes les requêtes correspondant à un document. Cette approche préserve l&#x27;importance relative de chaque document dans un ensemble de résultats tout en permettant un contrôle précis de l&#x27;influence de chaque requête sur le score final. Il s&#x27;agit donc d&#x27;un moyen plus intuitif et plus souple d&#x27;affiner la recherche hybride.</p><p>Définition d&#x27;un récupérateur linéaire où le score final sera calculé comme suit :</p><div class="katex">score = 5 * knn + 1.5 * bm25</div><p>C&#x27;est aussi simple que cela :</p><pre><code>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5
               },


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


           ]
       }
   }
}</code></pre><p>Cette approche combine le meilleur des deux mondes : elle conserve la flexibilité et la notation intuitive du <code>linear</code> retriever, tout en garantissant une mise à l&#x27;échelle cohérente des scores grâce à la normalisation MinMax.</p><p>Comme tous nos retrievers, le retriever <code>linear</code> peut être intégré à n&#x27;importe quel niveau d&#x27;un arbre hiérarchique de retrievers, avec une prise en charge de l&#x27;explicabilité, de la mise en évidence des correspondances, du regroupement des champs, etc.</p><h2>Quand choisir le retriever linéaire et pourquoi cela fait une différence</h2><p>Le <code>linear</code> retriever :</p><ul><li>Préserve l&#x27;importance relative en s&#x27;appuyant sur les scores réels, et pas seulement sur les rangs.</li><li>Permet un réglage fin avec des contributions pondérées de différentes requêtes.</li><li>Améliore la cohérence grâce à la normalisation, ce qui rend la recherche hybride plus robuste et plus prévisible.</li></ul><h2>Conclusion</h2><p>Le récupérateur <code>linear</code> est déjà disponible sur Elasticsearch Serverless, et les versions 8.18 et 9.0 ! D&#x27;autres exemples et paramètres de configuration sont également disponibles dans notre documentation. Essayez-le et voyez comment il peut améliorer votre expérience de la recherche hybride - nous attendons vos commentaires. Bonne recherche !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Création de listes de jugements avec Quepid]]></title>
            <description><![CDATA[Apprenez à créer des listes de jugement dans Quepid en utilisant un processus collaboratif d'évaluation humaine et à utiliser les références pour ajuster votre pertinence.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/quepid-judgement-lists</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/quepid-judgement-lists</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Daniel Wrigley]]></dc:creator>
            <pubDate>Mon, 26 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/307c1774bd31f92bb4aa7b69e1a6796240465100-1600x914.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La création de <a href="https://www.elastic.co/search-labs/blog/judgment-lists">listes de jugement</a> est une étape cruciale dans l&#x27;optimisation de la qualité des résultats de recherche, mais elle peut s&#x27;avérer une tâche compliquée et difficile. Une liste d&#x27;évaluation est un ensemble de requêtes de recherche associées à des notes de pertinence pour les résultats correspondants, également connu sous le nom de collection de tests. Les mesures calculées à l&#x27;aide de cette liste servent de référence pour mesurer les performances d&#x27;un moteur de recherche. Pour simplifier le processus de création des listes de jugement, l&#x27;équipe d&#x27;<a href="https://opensourceconnections.com/">OpenSource Connections</a> a développé <a href="https://quepidapp.com/">Quepid</a>. Le jugement peut être explicite ou basé sur un retour d&#x27;information implicite de la part des utilisateurs. Ce blog vous guidera dans la mise en place d&#x27;un environnement collaboratif dans Quepid afin de permettre aux évaluateurs humains d&#x27;effectuer des jugements explicites, ce qui est la base de toute liste de jugements.</p><p>Quepid soutient les équipes de recherche dans le processus d&#x27;évaluation de la qualité de la recherche :</p><ul><li>Construire des ensembles de requêtes</li><li>Créer des listes de jugement</li><li>Calculer les indicateurs de qualité de la recherche</li><li>Comparer différents algorithmes de recherche/rankers sur la base de paramètres de qualité de recherche calculés</li></ul><p>Pour notre blog, supposons que nous gérons un magasin de location de films et que notre objectif est d&#x27;améliorer la qualité de nos résultats de recherche.</p><h2>Produits requis</h2><p>Ce blog utilise les données et les correspondances du <a href="https://github.com/o19s/es-tmdb">référentiel es-tmdb</a>. Les données proviennent de <a href="https://www.themoviedb.org/">The Movie Database</a>. Pour continuer, créez un index appelé tmdb avec les mappings et indexez les données. Peu importe que vous mettiez en place une instance locale ou que vous utilisiez un déploiement Elastic Cloud pour cela - l&#x27;un ou l&#x27;autre fonctionne parfaitement. Pour ce blog, nous supposons un déploiement d&#x27;Elastic Cloud. Vous pouvez trouver des informations sur la manière d&#x27;indexer les données dans le <a href="https://github.com/o19s/es-tmdb/blob/master/README.md">README du dépôt es-tmdb</a>.</p><p>Effectuez une simple recherche de correspondance dans le champ du titre pour <code>rocky</code> afin de confirmer que vous disposez des données nécessaires à la recherche :</p><pre><code>GET tmdb/_search
{
 "query": {
   "match": {
     "title": "rocky"
   }
 }
}</code></pre><p>Vous devriez voir 8 résultats.</p><pre><code>{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 8,
     "relation": "eq"
   }
…
}</code></pre><h2>Se connecter à Quepid</h2><p><a href="https://github.com/o19s/quepid">Quepid</a> est un outil qui permet aux utilisateurs de mesurer la qualité des résultats de recherche et de mener des expériences hors ligne pour l&#x27;améliorer.</p><p>Vous pouvez utiliser Quepid de deux manières : soit vous utilisez la version gratuite et publiquement disponible à l&#x27;adresse <a href="https://app.quepid.com">https://app</a>.quepid.com, ou installez Quepid sur une machine à laquelle vous avez accès. Cet article suppose que vous utilisez la version hébergée gratuite. Si vous souhaitez mettre en place une instance de Quepid dans votre environnement, suivez le <a href="https://github.com/o19s/quepid/wiki/Installation-Guide">Guide d&#x27;installation</a>.</p><p>Quelle que soit la configuration choisie, vous devrez créer un compte si vous n&#x27;en avez pas déjà un.</p><h2>Comment configurer un cas Quepid</h2><p>Quepid est organisé autour des cas &quot;.&quot; Un cas stocke les requêtes avec les paramètres de réglage de la pertinence et la façon d&#x27;établir une connexion avec votre moteur de recherche.</p><ul><li>Pour les nouveaux utilisateurs, sélectionnez <strong>Créer votre premier cas de pertinence.</strong></li><li>Les utilisateurs qui reviennent peuvent sélectionner <strong>Cas de pertinence</strong> dans le menu de haut niveau et cliquer sur <strong>+ Créer un cas.</strong></li></ul><p>Donnez un nom descriptif à votre cas, par exemple &quot;Movie Search Baseline,&quot;, car nous voulons commencer à mesurer et à améliorer notre recherche de base.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f9e480d9aae0d706cfc5371e41f19c706dd452a-594x251.png" style="width: 100%;" /><p>Confirmez le nom en sélectionnant <strong>Continuer.</strong></p><p>Ensuite, nous établissons une connexion entre Quepid et le moteur de recherche. Quepid peut se connecter à une variété de moteurs de recherche, y compris Elasticsearch.</p><p>La configuration diffère en fonction de la configuration d&#x27;Elasticsearch et de Quepid. Pour connecter Quepid à un déploiement Elastic Cloud, nous devons activer et configurer CORS pour notre déploiement Elastic Cloud et avoir une clé API prête. Les instructions détaillées se trouvent dans le guide pratique correspondant dans la <a href="https://quepid-docs.dev.o19s.com/2/quepid/49/how-to-connect-quepid-to-elastic-cloud">documentation de</a> Quepid.</p><p>Saisissez les informations relatives à votre point de terminaison Elasticsearch (<code>https://YOUR_ES_HOST:PORT/tmdb/_search</code>) et toute autre information nécessaire à la connexion (la clé API dans le cas d&#x27;un déploiement Elastic Cloud dans les options de configuration <strong>avancées</strong> ), testez la connexion en cliquant sur <strong>ping it</strong> et sélectionnez <strong>Continue</strong> pour passer à l&#x27;étape suivante.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/267339dfaecae2740eb2ee2739bdc971608bdb5f-588x1169.png" style="width: 100%;" /><p>Nous définissons maintenant les champs que nous voulons voir apparaître dans le dossier. Sélectionner tout ce qui aide nos évaluateurs humains à évaluer ultérieurement la pertinence d&#x27;un document pour une requête donnée.</p><p>Définissez <code>title</code> comme <em>champ de titre</em>, laissez <code>_id</code> comme <em>champ d&#x27;identification</em> et ajoutez <code>overview, tagline, cast, vote_average, thumb:poster_path</code> comme <em>champs d&#x27;affichage supplémentaires</em>. La dernière entrée affiche de petites vignettes pour les films de nos résultats afin de nous guider visuellement, ainsi que les évaluateurs humains.</p><p>Confirmez les paramètres d&#x27;affichage en sélectionnant le bouton <strong>Continuer.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/02c58aae8c2ebb6d31f538b27462b4c65428fdc3-594x493.png" style="width: 100%;" /><p>La dernière étape consiste à ajouter des requêtes de recherche au dossier. Ajoutez les trois requêtes <em>star wars</em>, <em>harrison ford</em>, et <em>meilleur film d&#x27;action une</em> par une dans le champ de saisie et <strong>continuez</strong>.</p><p>Idéalement, un cas contient des requêtes qui représentent des requêtes réelles d&#x27;utilisateurs et illustrent différents types de requêtes. Pour l&#x27;instant, nous pouvons imaginer que <em>Star Wars</em> est une requête représentant toutes les demandes de titres de films, que <em>Harrison Ford</em> est une requête représentant toutes les demandes de noms d&#x27;acteurs, et que <em>le meilleur film d&#x27;action</em> est une requête représentant toutes les demandes de films d&#x27;un genre spécifique. C&#x27;est ce que l&#x27;on appelle généralement un ensemble de requêtes.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/baccfe96766319aa7255e9bff08913ac87d1517f-595x326.png" style="width: 100%;" /><p>Dans un scénario de production, nous échantillonnerions les requêtes à partir des données de suivi des événements en appliquant des techniques statistiques telles que l&#x27;<a href="https://opensourceconnections.com/blog/2022/10/13/how-to-succeed-with-explicit-relevance-evaluation-using-probability-proportional-to-size-sampling/">échantillonnage proportionnel à la taille</a> et importerions ces requêtes échantillonnées dans Quepid pour inclure les requêtes de la tête (requêtes fréquentes) et de la queue (requêtes peu fréquentes) par rapport à leur fréquence, ce qui signifie que nous privilégions les requêtes les plus fréquentes sans exclure les requêtes rares.</p><p>Enfin, sélectionnez <strong>Terminer</strong> et vous serez redirigé vers l&#x27;interface du cas où vous verrez les trois requêtes définies.</p><h2>Requêtes et besoins d&#x27;information</h2><p>Pour parvenir à l&#x27;objectif global d&#x27;une liste de jugement, les évaluateurs humains devront juger un résultat de recherche (généralement un document) pour une requête donnée. C&#x27;est ce qu&#x27;on appelle une paire requête/document.</p><p>Parfois, il semble facile de savoir ce que l&#x27;utilisateur voulait en regardant la requête. L&#x27;objectif de la requête <code>harrison ford</code> est de trouver des films mettant en scène l&#x27;acteur Harrison Ford. Qu&#x27;en est-il de la requête <code>action</code>? Je sais que je serais tenté de dire que l&#x27;intention de l&#x27;utilisateur est de trouver des films appartenant au genre action. Mais lesquels ? Les plus récents, les plus populaires, les meilleurs d&#x27;après les évaluations des utilisateurs ? Ou bien l&#x27;utilisateur souhaite-t-il trouver tous les films intitulés &quot;Action&quot; ? <a href="https://www.themoviedb.org/search/movie?query=Action">Il y a au moins 12 ( !) films appelés &quot;Action&quot; dans The Movie Database</a> et leurs noms diffèrent principalement par le nombre de points d&#x27;exclamation dans le titre.</p><p>Deux évaluateurs humains peuvent interpréter différemment une requête dont l&#x27;intention n&#x27;est pas claire. Le besoin d&#x27;information : Un <a href="https://en.wikipedia.org/wiki/Information_needs">besoin</a> d&#x27;information est un désir conscient ou inconscient d&#x27;obtenir des informations. La définition d&#x27;un besoin d&#x27;information aide les évaluateurs humains à juger les documents pour une requête, et joue donc un rôle important dans le processus d&#x27;élaboration des listes de jugement. Les utilisateurs experts ou les experts en la matière sont de bons candidats pour spécifier les besoins d&#x27;information. Une bonne pratique consiste à définir les besoins d&#x27;information du point de vue de l&#x27;utilisateur, car c&#x27;est à ses besoins que les résultats de la recherche doivent répondre.</p><p>Besoins en informations pour les requêtes de notre cas &quot;Recherche de films de référence&quot; :</p><ol><li><strong>la guerre des étoiles</strong>: L&#x27;utilisateur souhaite trouver des films ou des émissions de la franchise Star Wars. Les documentaires sur la Guerre des étoiles sont potentiellement pertinents.</li><li><strong>harrison ford</strong>: L&#x27;utilisateur souhaite trouver des films mettant en scène l&#x27;acteur Harrison Ford. Les films dans lesquels Harrison Ford joue un rôle différent, comme celui de narrateur, sont potentiellement pertinents.</li><li><strong>meilleur film d&#x27;action</strong>: L&#x27;utilisateur souhaite trouver des films d&#x27;action, de préférence ceux dont la moyenne des votes des utilisateurs est élevée.</li></ol><h2>Comment définir les besoins en information dans Quepid</h2><p>Pour définir un besoin d&#x27;information dans Quepid, accédez à l&#x27;interface du cas :</p><p>1. Ouvrez une requête (par exemple<em>star wars</em>) et sélectionnez <em>Toggle Notes.</em></p><p>2. Inscrivez le besoin d&#x27;information dans le premier champ et toute note supplémentaire dans le deuxième champ :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e01d3d5242a350d8797faa665eb3170039f5dfa2-1483x559.png" style="width: 100%;" /><p>3. Cliquez sur <strong>Enregistrer</strong>.</p><p>Pour une poignée de requêtes, ce processus est satisfaisant. Cependant, lorsque vous passez de trois à 100 requêtes (les cas Quepid sont souvent compris entre 50 et 100 requêtes), vous pouvez définir les besoins d&#x27;information en dehors de Quepid (par exemple, dans une feuille de calcul), puis les télécharger via <strong>Import</strong> et sélectionner <strong>Besoins d&#x27;information.</strong></p><h2>Créez une équipe dans Quepid et partagez votre cas</h2><p>Les jugements collaboratifs améliorent la qualité des évaluations de la pertinence. Mettre en place une équipe :</p><p>1. Naviguez jusqu&#x27;à <strong>Équipes</strong> dans le menu de haut niveau.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/797706e8d130b474a95d30b6fa22ecaf36f98c03-613x58.png" style="width: 100%;" /><p>2. Cliquez sur <strong>+ Ajouter nouveau</strong>, saisissez un nom d&#x27;équipe (par exemple, &quot;Search Relevance Raters&quot;), puis cliquez sur <strong>Créer.</strong></p><p>3. Ajoutez des membres en saisissant leur adresse électronique et en cliquant sur <strong>Ajouter un utilisateur</strong>.</p><p>4. Dans l&#x27;interface du dossier, sélectionnez <strong>Partager le dossier.</strong></p><p>5. Choisissez l&#x27;équipe appropriée et confirmez.</p><h2>Créer un livre de jugements dans Quepid</h2><p>Un livre dans Quepid permet à plusieurs évaluateurs d&#x27;évaluer systématiquement les paires requête/document. Pour en créer un :</p><p>1. Dans l&#x27;interface du dossier, allez dans <strong>Jugements</strong> et cliquez sur <strong>+ Créer un livre.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6a077f26225961150b7414463d7db04f090b68d6-896x365.png" style="width: 100%;" /><p>2. Configurez le livre avec un nom descriptif, assignez-le à votre équipe, sélectionnez une méthode de notation (par exemple, DCG@10) et définissez la stratégie de sélection (un ou plusieurs évaluateurs). Utilisez les paramètres suivants pour le livre :</p><ul><li><strong>Nom</strong>: &quot;Recherche de films échelle 0-3&quot;</li><li><strong>Équipes avec lesquelles partager ce livre</strong>: Cochez la case de l&#x27;équipe que vous avez créée.</li><li><strong>Marqueur</strong>: DCG@10</li></ul><p>3. Cliquez sur <strong>Créer un livre.</strong></p><p>Le nom est descriptif et contient des informations sur l&#x27;objet de la recherche (&quot;Films&quot;) ainsi que l&#x27;échelle des jugements (&quot;0-3&quot;). Le scoreur DCG@10 sélectionné définit la manière dont la métrique de recherche sera calculée. &quot;DCG&quot; est l&#x27;abréviation de <a href="https://en.wikipedia.org/wiki/Discounted_cumulative_gain">Discounted Cumulative Gain (gain cumulatif actualisé</a> ) et &quot;@10&quot; est le nombre de résultats à partir du sommet pris en considération lors du calcul de la mesure.</p><p>Dans ce cas, nous utilisons un indicateur qui mesure le gain d&#x27;information et le combine avec la pondération positionnelle. D&#x27;autres mesures de recherche peuvent être plus adaptées à votre cas d&#x27;utilisation et le <a href="https://opensourceconnections.com/blog/2020/02/28/choosing-your-search-relevance-metric">choix de la bonne mesure est un défi en soi.</a></p><h2>Remplir le book avec des paires requête/document</h2><p>Afin d&#x27;ajouter des paires requête/document pour l&#x27;évaluation de la pertinence, suivez les étapes suivantes :</p><p>1. Dans l&#x27;interface du cas, naviguez vers &quot;Judgements.&quot;</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d717c5b06ae6cb42ed2b9e771486a12f738a9890-1041x218.png" style="width: 100%;" /><p>2. Sélectionnez le livre que vous avez créé.</p><p>3. Cliquez sur &quot;Populate Book&quot; et confirmez en sélectionnant &quot;Refresh Query/Doc Pairs for Book.&quot;</p><p>Cette action génère des paires basées sur les meilleurs résultats de recherche pour chaque requête, prêtes à être évaluées par votre équipe.</p><h2>Laissez votre équipe d’évaluateurs humains juger </h2><p>Jusqu&#x27;à présent, les étapes franchies étaient plutôt techniques et administratives. Maintenant que cette préparation nécessaire est faite, nous pouvons laisser notre équipe de juges faire son travail. En substance, le travail du juge consiste à évaluer la pertinence d&#x27;un document particulier pour une requête donnée. Le résultat de ce processus est la liste de jugement qui contient tous les labels de pertinence pour les paires de documents de la requête jugés. Ensuite, ce processus et son interface sont expliqués plus en détail.</p><h3>Aperçu de l&#x27;interface Human Rating</h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/be3016091b49655dab3354d84e6dc638f3468390-1283x664.png" style="width: 100%;" /><p>L&#x27;interface Human Rating de Quepid est conçue pour des évaluations efficaces :</p><ul><li><strong>Requête :</strong> Affiche le terme de la recherche.</li><li><strong>Besoin d&#x27;information :</strong> Indique l&#x27;intention de l&#x27;utilisateur.</li><li><strong>Lignes directrices pour la notation :</strong> Fournit des instructions pour des évaluations cohérentes.</li><li><strong>Métadonnées du document :</strong> Présente des détails pertinents sur le document.</li><li><strong>Boutons d&#x27;évaluation :</strong> Permet aux évaluateurs d&#x27;attribuer des jugements à l&#x27;aide des raccourcis clavier correspondants.</li></ul><h3>Utilisation de l&#x27;interface Human Rating</h3><p>En tant qu&#x27;évaluateur humain, j&#x27;accède à l&#x27;interface via l&#x27;aperçu du livre :</p><p>1. Accédez à l&#x27;interface du dossier et cliquez sur <strong>Jugements</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d717c5b06ae6cb42ed2b9e771486a12f738a9890-1041x218.png" style="width: 100%;" /><p>2. Cliquez sur <strong>Plus de jugements sont nécessaires !</strong></p><p>Le système présentera une paire requête/document qui n&#x27;a pas encore été évaluée et qui nécessite des jugements supplémentaires. Elle est déterminée par la stratégie de sélection du Livre :</p><ul><li>Un <em>seul évaluateur</em>: Un seul jugement par paire requête/document.</li><li><em>Plusieurs évaluateurs</em>: Jusqu&#x27;à trois évaluations par paire requête/document.</li></ul><h3>Évaluation des paires requête/document</h3><p>Voyons quelques exemples. Lorsque vous suivrez ce guide, vous serez probablement confronté à différents films. Cependant, les principes de notation restent les mêmes.</p><p>Notre premier exemple est le film &quot;Heroes&quot; pour la requête <em>harrison ford</em>:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1e14b0d8b04dd579471932dbe4ff72ae5692a02-981x571.png" style="width: 100%;" /><p>Nous examinons d&#x27;abord la requête, puis le besoin d&#x27;information et jugeons ensuite le film sur la base des métadonnées fournies.</p><p>Ce film est un résultat pertinent pour notre requête, puisque Harridson Ford en fait partie. Nous pouvons considérer subjectivement que les films plus récents sont plus pertinents, mais cela ne fait pas partie de notre besoin d&#x27;information. Nous attribuons donc à ce document la note &quot;parfait&quot;, qui correspond à un 3 dans notre échelle de notation.</p><p>Notre prochain exemple est le film &quot;Ford contre Ferrari&quot; pour la requête <em>harrison ford</em>:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/052af7894506d7a765af156ba8e26ceec3559973-981x789.png" style="width: 100%;" /><p>Selon la même pratique, nous évaluons cette requête/document en examinant la requête, le besoin d&#x27;information et la correspondance entre les métadonnées du document et le besoin d&#x27;information.</p><p>C&#x27;est un mauvais résultat. Ce résultat est probablement dû au fait que l&#x27;un des termes de notre requête, &quot;ford&quot;, figure dans le titre. Mais Harrison Ford ne joue aucun rôle dans ce film, ni dans aucun autre. Nous attribuons donc à ce document la note &quot;médiocre&quot;, ce qui correspond à un 0 dans notre échelle de notation.</p><p>Notre troisième exemple est le film &quot;Action Jackson&quot; pour la requête <em>meilleur film d&#x27;action :</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/247ab862fbc7435537709f8c96619cb331133d09-985x606.png" style="width: 100%;" /><p>Cela ressemble à un film d&#x27;action, donc le besoin d&#x27;information est au moins partiellement satisfait. Cependant, la moyenne des votes est de 5,4 sur 10. Ce qui fait que ce film n&#x27;est probablement pas le meilleur film d&#x27;action de notre collection. Cela m&#x27;amènerait, en tant que juge, à attribuer à ce document la note &quot;Passable&quot;, qui correspond à la note 1 dans notre échelle de notation.</p><p>Ces exemples illustrent le processus d&#x27;évaluation des paires requête/document avec Quepid en particulier, à un niveau élevé et aussi en général.</p><h2>Bonnes pratiques pour les évaluateurs humains</h2><p>Les exemples présentés peuvent donner l&#x27;impression qu&#x27;il est facile de parvenir à des jugements explicites. Mais la mise en place d&#x27;un programme d&#x27;évaluation humaine fiable n&#x27;est pas une mince affaire. C&#x27;est un processus plein de défis qui peut facilement compromettre la qualité de vos données :</p><ul><li>Les évaluateurs humains peuvent être fatigués par des tâches répétitives.</li><li>Les préférences personnelles peuvent fausser les jugements.</li><li>Les niveaux d&#x27;expertise varient d&#x27;un juge à l&#x27;autre.</li><li>Les évaluateurs jonglent souvent avec de multiples responsabilités.</li><li>La pertinence perçue d&#x27;un document peut ne pas correspondre à sa pertinence réelle par rapport à une requête.</li></ul><p>Ces facteurs peuvent entraîner des jugements incohérents et de faible qualité. Mais ne vous inquiétez pas - il existe des bonnes pratiques éprouvées qui peuvent vous aider à minimiser ces problèmes et à mettre en place un processus d&#x27;évaluation plus solide et plus fiable :</p><ul><li><strong>Évaluation cohérente :</strong> Examiner la requête, le besoin d&#x27;information et les métadonnées du document dans l&#x27;ordre.</li><li><strong>Se référer aux lignes directrices :</strong> Utiliser les lignes directrices pour la notation afin de maintenir la cohérence. Les lignes directrices en matière de notation peuvent contenir des exemples illustrant le processus de jugement et indiquant quand appliquer telle ou telle note. La vérification des évaluateurs humains après le premier lot de jugements s&#x27;est avérée être une bonne pratique pour connaître les cas limites difficiles et les domaines dans lesquels une aide supplémentaire est nécessaire.</li><li><strong>Utilisez les options :</strong> En cas d&#x27;incertitude, utilisez &quot;I Will Judge Later&quot; ou &quot;I Can&#x27;t Tell,&quot; en fournissant des explications si nécessaire.</li><li><strong>Faire des pauses :</strong> Des pauses régulières permettent de maintenir la qualité du jugement. Quepid facilite les pauses régulières en lançant des confettis chaque fois qu&#x27;un évaluateur humain termine un lot de jugements.</li></ul><p>En suivant ces étapes, vous établissez une approche structurée et collaborative pour créer des listes d&#x27;arrêts dans Quepid, améliorant ainsi l&#x27;efficacité de vos efforts d&#x27;optimisation de la pertinence des recherches.</p><h2>Étapes suivantes</h2><p>Que faire maintenant ? Les listes de jugement ne sont qu&#x27;une étape fondamentale vers l&#x27;amélioration de la qualité des résultats de recherche. Voici les prochaines étapes :</p><h3>Calculer les indicateurs et commencer à expérimenter</h3><p>Une fois que les listes de jugements sont disponibles, l&#x27;exploitation des jugements et le calcul des <a href="https://opensourceconnections.com/blog/2020/02/28/choosing-your-search-relevance-metric/">mesures de la qualité de la recherche</a> constituent une progression naturelle. Quepid calcule automatiquement la mesure configurée pour l&#x27;affaire en cours lorsque les jugements sont disponibles. Les mesures sont implémentées en tant que &quot;Scorers&quot; et vous pouvez fournir les vôtres lorsque les mesures supportées n&#x27;incluent pas vos préférées !</p><p>Accédez à l&#x27;interface du cas, naviguez jusqu&#x27;à <strong>Sélectionner un évaluateur</strong>, choisissez <em>DCG@10</em> et confirmez en cliquant sur <strong>Sélectionner un évaluateur</strong>. Quepid va maintenant calculer le DCG@10 par requête ainsi que la moyenne des requêtes globales afin de quantifier la qualité des résultats de recherche pour votre cas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ff3b9918342071744d681a43d542102e927abd3-1163x551.png" style="width: 100%;" /><p>Maintenant que la qualité de vos résultats de recherche est quantifiée, vous pouvez lancer les premières expériences. L&#x27;expérimentation commence par la formulation d&#x27;hypothèses. En examinant les trois requêtes de la capture d&#x27;écran après avoir effectué un classement, il est évident que les trois requêtes ont des performances très différentes en termes de qualité de recherche : <em>la guerre des étoiles</em> est assez performante, <em>harrison ford</em> semble correct mais le plus grand potentiel réside dans le <em>meilleur film d&#x27;action.</em></p><p>En développant cette requête, nous voyons ses résultats et pouvons nous plonger dans les détails les plus infimes et explorer les raisons pour lesquelles les documents correspondent et ce qui influence leurs scores :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c45c6cae085a492198c0f8b7060a1a7204e3724e-1131x691.png" style="width: 100%;" /><p>En cliquant sur &quot;Explain Query&quot; et en entrant dans l&#x27;onglet &quot;Parsing&quot;, nous voyons que la requête est une DisjunctionMaxxQuery qui recherche trois champs : <em>cast</em>, <em>overview</em> et <em>title</em>:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4a1e1bb2a9cd28e9c48e0ba16357d17ed9d3a5cf-894x557.png" style="width: 100%;" /><p>En général, en tant qu&#x27;ingénieurs de recherche, nous connaissons certaines spécificités de notre plateforme de recherche. Dans ce cas, nous pouvons savoir que nous avons un champ de <em>genre</em>. Ajoutons-le à la requête et voyons si la qualité de la recherche est améliorée.</p><p>Nous utilisons l&#x27;<strong>Environnement de recherche</strong> qui s&#x27;ouvre lorsque l&#x27;on sélectionne <strong>Accorder la pertinence</strong> dans l&#x27;interface du cas. Allez-y et explorez cette possibilité en ajoutant le champ des <em>genres</em> dans lequel vous effectuez votre recherche :</p><pre><code>{
  "query": {
    "multi_match": {
      "query": "#$query##",
      "type": "best_fields",
      "fields": [
        "title^10",
        "overview",
        "cast",
        "genres"
      ]
    }
  }
}</code></pre><p>Cliquez sur Réexécuter mes recherches ! Et consultez les résultats. Ont-ils changé ? Malheureusement, ce n&#x27;est pas le cas. Nous avons maintenant beaucoup d&#x27;options à explorer, essentiellement toutes les options de requête offertes par Elasticsearch :</p><ul><li>Nous pourrions augmenter le poids du champ sur le champ des genres.</li><li>Nous pourrions ajouter une fonction qui augmente les documents en fonction de leur moyenne de votes.</li><li>Nous pourrions créer une requête plus complexe qui n&#x27;augmenterait les documents en fonction de leur moyenne de vote que s&#x27;il existe une forte correspondance des genres.</li><li>…</li></ul><p>L&#x27;avantage d&#x27;avoir toutes ces options et de les explorer dans Quepid est que nous avons un moyen de quantifier les effets non seulement sur la requête que nous essayons d&#x27;améliorer, mais aussi sur toutes les requêtes que nous avons dans notre cas. Cela nous évite d&#x27;améliorer une requête peu performante en sacrifiant la qualité des résultats de recherche pour les autres. Nous pouvons itérer rapidement et à peu de frais et valider la valeur de notre hypothèse sans aucun risque, ce qui fait de l&#x27;expérimentation hors ligne une capacité fondamentale pour toutes les équipes de recherche.</p><h3>Mesurer la fiabilité inter-évaluateurs</h3><p>Même avec des descriptions de tâches, des besoins d&#x27;information et une interface d&#x27;évaluation humaine comme celle fournie par Quepid, les évaluateurs humains peuvent ne pas être d&#x27;accord.</p><p>Le désaccord en soi n&#x27;est pas une mauvaise chose, bien au contraire : mesurer le désaccord peut mettre en lumière des questions que vous pourriez vouloir aborder. La pertinence peut être subjective, les requêtes peuvent être ambiguës et les données peuvent être incomplètes ou incorrectes. Le <a href="https://en.wikipedia.org/wiki/Fleiss%27_kappa">Kappa de Fleiss</a> est une mesure statistique de l&#x27;accord entre les évaluateurs et il existe un cahier d&#x27;exemples dans Quepid que vous pouvez utiliser. Pour le trouver, sélectionnez <strong>Notebooks</strong> dans la navigation de haut niveau et sélectionnez le carnet <strong>Fleiss Kappa.ipynb</strong> dans le dossier <strong>examples.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f0669ae96371368ef4d84bb28669560ef09d755c-624x61.png" style="width: 100%;" /><h2>Conclusion</h2><p>Quepid vous permet de relever les défis les plus complexes en matière de pertinence des recherches et continue d&#x27;évoluer : <a href="https://github.com/o19s/quepid/blob/main/CHANGELOG.md#800----2024-02-14">à partir de la version 8, Quepid prend en charge les jugements générés par l&#x27;IA</a>, ce qui est particulièrement utile pour les équipes qui souhaitent faire évoluer leur processus de génération de jugements.</p><p>Les flux de travail de Quepid vous permettent de créer efficacement des listes d&#x27;arrêts évolutives, ce qui se traduit par des résultats de recherche qui répondent réellement aux besoins des utilisateurs. Une fois les listes de jugement établies, vous disposez d&#x27;une base solide pour mesurer la pertinence des recherches, procéder à des améliorations itératives et améliorer l&#x27;expérience des utilisateurs.</p><p>N&#x27;oubliez pas que l&#x27;optimisation de la pertinence est un processus continu. Les listes de jugement vous permettent d&#x27;évaluer systématiquement vos progrès, mais elles sont plus efficaces lorsqu&#x27;elles sont associées à l&#x27;expérimentation, à l&#x27;analyse des mesures et aux améliorations itératives.</p><h2>Lecture complémentaire</h2><ul><li>Docs Quepid :<ul><li><a href="https://quepid-docs.dev.o19s.com/2/quepid/32/relevancy-is-a-team-sport">La pertinence est un sport d&#x27;équipe</a></li><li><a href="https://quepid-docs.dev.o19s.com/2/quepid/18/quepid-for-human-raters">Quepid pour les évaluateurs humains</a></li><li><a href="https://quepid-docs.dev.o19s.com/2/quepid/49/how-to-connect-quepid-to-elastic-cloud">Comment connecter Quepid à Elastic Cloud ?</a></li></ul></li><li><a href="https://github.com/o19s/quepid">Dépôt Github de Quepid</a></li><li><a href="https://opensourceconnections.com/blog/2020/07/07/meet-pete-the-e-commerce-search-product-manager/">Meet Pete, une série de blogs sur l&#x27;amélioration de la recherche dans le domaine du commerce électronique</a></li><li><a href="https://opensourceconnections.com/slack">Relevance Slack</a>: rejoignez le canal #quepid</li></ul><p><strong>Associez-vous à <a href="https://opensourceconnections.com/">Open Source Connections</a></strong> pour transformer vos capacités de recherche et d&#x27;IA et donner à votre équipe les moyens de les faire évoluer en permanence. Nous avons fait nos preuves dans le monde entier et nos clients ont constamment amélioré la qualité de leurs recherches, les capacités de leurs équipes et les performances de leurs entreprises. <a href="https://opensourceconnections.com/contact/">Contactez-nous dès aujourd&#x27;hui</a> pour en savoir plus.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Affichage des champs dans un index Elasticsearch]]></title>
            <description><![CDATA[Exploration des techniques d'affichage des champs dans un index Elasticsearch.
]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/displaying-fields-in-an-elasticsearch-index</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/displaying-fields-in-an-elasticsearch-index</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Kofi Bartlett]]></dc:creator>
            <pubDate>Mon, 26 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fa386d7bbaeab6855e62897ace8d7dca91a060b4-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans cet article, nous verrons comment afficher des champs dans un index Elasticsearch. Cela peut être utile pour comprendre la structure de vos données, identifier des champs spécifiques et résoudre des problèmes. Nous aborderons les sujets suivants :</p><ol><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#1.-using-the--mapping-api-to-retrieve-field-information">Utilisation de l&#x27;<code>_mapping</code> API pour récupérer des informations sur les champs</a></li><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#2.-using-the--search-api-to-display-field-values">Utilisation de l&#x27;<code>_search</code> API pour afficher les valeurs des champs</a></li><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#3.-filtering-fields-using-the-fields-parameter">Filtrage des champs à l&#x27;aide du <code>fields</code> paramètre</a></li><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#4.-displaying-nested-fields">Affichage des champs imbriqués</a></li></ol><h2>1. Utilisation de l&#x27;API _mapping pour récupérer des informations sur les champs</h2><p>L&#x27;API <code>_mapping</code> vous permet de récupérer la définition du mappage pour un ou plusieurs <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-index/">index.</a> Il s&#x27;agit d&#x27;informations sur les champs, leurs types de données et d&#x27;autres propriétés. Pour récupérer le mappage d&#x27;un index spécifique, utilisez la requête suivante :</p><pre><code>GET /<index_name>/_mapping</code></pre><p>Par exemple, si vous avez un index nommé <code>my_index</code>, vous pouvez récupérer son mapping avec la requête suivante :</p><pre><code>GET /my_index/_mapping</code></pre><p>La réponse comprendra la définition du mappage pour l&#x27;index, qui contient des informations sur les champs et leurs propriétés.</p><p>Il est également possible de récupérer la cartographie d&#x27;un champ spécifique. Cela peut s&#x27;avérer utile si votre cartographie est assez vaste et que vous souhaitez vous concentrer sur un domaine spécifique. Pour récupérer la correspondance d&#x27;un champ spécifique, utilisez la requête suivante :</p><pre><code>GET /my_index/_mapping/field/my_field</code></pre><p>Vous pouvez également récupérer les correspondances de plusieurs champs en séparant leurs noms par des virgules, comme dans la requête suivante :</p><pre><code>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3</code></pre><h2>2. Utilisation de l&#x27;API _search pour afficher les valeurs des champs</h2><p>Pour afficher les valeurs des champs d&#x27;un index Elasticsearch, vous pouvez utiliser l&#x27;API <code>_search</code>. Par défaut, l&#x27;API <code>_search</code> renvoie le champ <code>_source</code>, qui contient le document JSON original qui a été indexé. Pour n&#x27;afficher que des champs spécifiques, vous pouvez utiliser le paramètre <code>_source</code> dans la requête de recherche.</p><p>Voici un exemple de demande de recherche qui renvoie les valeurs des champs <code>title</code> et <code>author</code> pour les documents de l&#x27;index <code>my_index</code>:</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}</code></pre><p>Dans cet exemple, le paramètre <code>_source</code> spécifie les champs à renvoyer.</p><h2>3. Filtrer les champs à l&#x27;aide du paramètre fields</h2><p>Vous pouvez également utiliser le paramètre <code>fields</code> pour filtrer les champs renvoyés dans la réponse de recherche. Cela peut être utile si vous n&#x27;avez besoin que de champs spécifiques et que vous souhaitez réduire la taille de la réponse. Le paramètre <code>fields</code> accepte un tableau de noms de champs ou de caractères génériques.</p><p>Par exemple, pour obtenir uniquement les champs <code>title</code> et <code>author</code> pour les documents de l&#x27;index <code>my_index</code>, vous pouvez utiliser la requête de recherche suivante :</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}</code></pre><p>Notez que le paramètre <code>_source</code> est fixé à false afin de ne pas renvoyer le document source.</p><p>Pour obtenir tous les champs dont le type de données est <code>text</code>, vous pouvez utiliser un motif joker comme celui-ci :</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["*.text"],
  "_source": false
}</code></pre><h2>4. Affichage des champs imbriqués</h2><p>Si votre index contient des champs imbriqués, vous pouvez utiliser la notation point pour spécifier le chemin du champ imbriqué dans le paramètre <code>fields</code>. Par exemple, si vous disposez d&#x27;un champ imbriqué nommé <code>address.city</code>, vous pouvez l&#x27;inclure dans la réponse de recherche comme suit :</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}</code></pre><p>Dans cet exemple, la réponse de la recherche comprendra les valeurs des champs <code>title</code>, <code>author</code> et <code>address.city</code>.</p><h2>Conclusion</h2><p>En conclusion, l&#x27;affichage des champs dans un index Elasticsearch peut être réalisé en utilisant l&#x27;API <code>_mapping</code> pour récupérer les informations sur les champs et l&#x27;API <code>_search</code> pour afficher les valeurs des champs. Vous pouvez filtrer les champs renvoyés dans la réponse de recherche à l&#x27;aide des paramètres <code>_source</code> ou <code>fields</code> et afficher les champs imbriqués à l&#x27;aide de la notation par points. Ces techniques peuvent vous aider à comprendre la structure de vos données, à identifier des champs spécifiques et à résoudre des problèmes.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring AI et Elasticsearch comme base de données vectorielle]]></title>
            <description><![CDATA[Apprenez à créer une application RAG prête pour la production en utilisant Spring IA et Elasticsearch et à intégrer des LLM à vos données propriétaires à l’aide d’une Base vectorielle.
]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/spring-ai-elasticsearch-application</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/spring-ai-elasticsearch-application</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Philipp Krenn, Laura Trotta]]></dc:creator>
            <pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0771fb5b3d9234697cb868cd7d9d1b840000bf29-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Spring AI</strong> est maintenant disponible, avec sa première <a href="https://spring.io/blog/2025/05/20/spring-ai-1-0-GA-released">version stable 1.0</a> prête à être téléchargée sur <a href="https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-core">Maven Central.</a> Utilisons-le immédiatement pour construire une application d&#x27;IA complète, en utilisant votre <a href="https://www.elastic.co/what-is/large-language-models">LLM</a> favori et notre <a href="https://www.elastic.co/elasticsearch/vector-database">base de données vectorielle</a> préférée. Ou plongez directement dans le <a href="https://github.com/xeraa/rag-with-java-springai-elasticsearch">référentiel</a> avec l&#x27;application finale.</p><a href="https://www.youtube.com/watch/ms2xRO8B1YU">Watch video on YouTube</a><h2>Qu&#x27;est-ce que l&#x27;IA de printemps ?</h2><p><strong>Spring AI 1.0</strong>, une solution complète pour l&#x27;ingénierie de l&#x27;IA en Java, est maintenant disponible après une période de développement significative influencée par les progrès rapides dans le domaine de l&#x27;IA. Cette version comprend de nombreuses nouvelles fonctionnalités essentielles pour les ingénieurs en IA.</p><p>Java et Spring sont les mieux placés pour profiter de la vague de l&#x27;IA. Des tonnes d&#x27;entreprises utilisent Spring Boot, ce qui facilite grandement l&#x27;intégration de l&#x27;IA dans ce qu&#x27;elles font déjà. Vous pouvez relier votre logique d&#x27;entreprise et vos données à ces modèles d&#x27;IA sans trop de difficultés.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/328d7c51e1c145e94ea1e73ee9ff91836d3b180e-1600x773.png" style="width: 100%;" /><p>Spring AI prend en charge <a href="https://docs.spring.io/spring-ai/reference/api/index.html">différents modèles et technologies d&#x27;IA</a>, tels que :</p><ul><li><strong>Modèles d&#x27;images</strong>: générer des images à partir de textes.</li><li><strong>Modèles de transcription</strong>: prendre des sources audio et les convertir en texte.</li><li><strong>Modèles d&#x27;intégration : </strong>convertissent des données arbitraires en <a href="https://www.elastic.co/what-is/vector-embedding">vecteurs</a>, qui sont des types de données optimisés pour la recherche de similarités sémantiques.</li><li><strong>Modèles de chat : </strong>ces<strong> </strong>devraient vous être familiers ! Vous avez sans doute déjà eu une brève conversation avec l&#x27;un d&#x27;entre eux.</li></ul><p>Les modèles de dialogue en ligne sont ceux qui semblent le plus faire parler d&#x27;eux dans le domaine de l&#x27;IA, et à juste titre, ils sont géniaux ! Ils peuvent vous aider à corriger un document ou à écrire un poème. (Ne leur demandez pas de raconter une blague... pour l&#x27;instant). Ils sont géniaux, mais ils ont quelques problèmes.</p><h2>Des solutions d&#x27;IA de printemps pour relever les défis de l&#x27;IA</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2ebd68a90ebc73847df6ef7325936d4d06b35c8c-1600x900.jpg" style="width: 100%;" /><p>Examinons quelques-uns de ces problèmes et leurs solutions dans Spring AI.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Problème</td><td style="border: 1px solid #ccc; padding: 4px;">Solution</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Cohérence</td><td style="border: 1px solid #ccc; padding: 4px;">Les modèles de chat sont ouverts d'esprit et enclins à la distraction.</td><td style="border: 1px solid #ccc; padding: 4px;">Vous pouvez les doter d'un système d'incitation pour régir leur forme et leur structure générales</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Mémoire</td><td style="border: 1px solid #ccc; padding: 4px;">Les modèles d'IA n'ont pas de mémoire et ne peuvent donc pas établir de corrélation entre les messages d'un utilisateur donné et ceux d'un autre utilisateur.</td><td style="border: 1px solid #ccc; padding: 4px;">Vous pouvez leur donner un système de mémoire pour stocker les parties pertinentes de la conversation</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">L'isolement</td><td style="border: 1px solid #ccc; padding: 4px;">Les modèles d'IA vivent dans de petits bacs à sable isolés, mais ils peuvent faire des choses vraiment étonnantes si vous leur donnez accès à des outils - des fonctions qu'ils peuvent invoquer lorsqu'ils le jugent nécessaire</td><td style="border: 1px solid #ccc; padding: 4px;">Spring AI prend en charge l'appel d'outils, ce qui vous permet d'indiquer au modèle d'IA les outils présents dans son environnement, qu'il peut ensuite vous demander d'invoquer. Cette interaction à plusieurs tours est gérée de manière transparente pour vous</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Données privées</td><td style="border: 1px solid #ccc; padding: 4px;">Les modèles d'IA sont intelligents, mais ils ne sont pas omniscients ! Ils ne savent pas ce que contiennent vos bases de données propriétaires - et nous pensons que vous ne voudriez pas qu'ils le sachent !</td><td style="border: 1px solid #ccc; padding: 4px;">Vous devez informer leurs réponses en remplissant les invites, c'est-à-dire en utilisant le puissant opérateur de concaténation de chaînes pour insérer du texte dans la demande avant que le modèle n'examine la question posée. Des informations de base, si vous voulez. Comment décidez-vous de ce qui doit être envoyé et de ce qui ne doit pas l'être ? Utilisez un magasin de vecteurs pour sélectionner uniquement les données pertinentes et les envoyer ensuite. C'est ce que l'on appelle la génération augmentée par récupération (RAG).</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Hallucination</td><td style="border: 1px solid #ccc; padding: 4px;">Les modèles de chat de l'IA aiment discuter ! Et parfois, ils le font avec tant d'assurance qu'ils peuvent inventer des choses</td><td style="border: 1px solid #ccc; padding: 4px;">Vous devez recourir à l'évaluation - en utilisant un modèle pour valider les résultats d'un autre - pour confirmer des résultats raisonnables.</td>
                    </tr>
              </tbody>
            </table>
          <p></p><p>Bien entendu, aucune application d&#x27;IA n&#x27;est isolée. Aujourd&#x27;hui, les systèmes et services d&#x27;IA modernes fonctionnent mieux lorsqu&#x27;ils sont intégrés à d&#x27;autres systèmes et services. <strong><a href="https://modelcontextprotocol.io/introduction">Model Context Protocol</a> </strong>(MCP) permet de connecter vos applications d&#x27;intelligence artificielle à d&#x27;autres services basés sur MCP, quel que soit le langage dans lequel elles sont écrites. Vous pouvez assembler tous ces éléments dans des flux de travail <strong>agentiques </strong>qui mènent à un objectif plus large.</p><p>Le plus beau ? Vous pouvez faire tout cela en vous appuyant sur les idiomes et abstractions familiers auxquels tout développeur Spring Boot est habitué : des dépendances de départ pratiques pour pratiquement tout sont disponibles sur <strong><a href="https://start.spring.io">Spring Initializr</a>.</strong></p><p>Spring AI fournit des autoconfigurations Spring Boot pratiques qui vous donnent la configuration conventionnelle sur la configuration que vous connaissez et attendez. Spring AI prend en charge l&#x27;observabilité avec l&#x27;Actuator de Spring Boot et le projet Micrometer. Il est également compatible avec GraalVM et les threads virtuels, ce qui vous permet de créer des applications d&#x27;IA super rapides et efficaces qui évoluent.</p><h2>Pourquoi Elasticsearch ?</h2><p>Elasticsearch est un moteur de recherche plein texte, vous le savez probablement. Alors pourquoi l&#x27;utiliser pour ce projet ? C&#x27;est <em>aussi</em> un magasin de vecteurs ! Il s&#x27;agit d&#x27;un excellent outil, dans lequel les données sont placées à côté du texte intégral. Autres avantages notables :</p><ul><li>Très facile à mettre en place</li><li>Opensource</li><li>Évolutivité horizontale</li><li>La plupart des données libres de votre organisation se trouvent probablement déjà dans un cluster Elasticsearch.</li><li>Fonctionnalité de moteur de recherche complète</li><li>Entièrement <a href="https://docs.spring.io/spring-ai/reference/api/vectordbs/elasticsearch.html">intégré à Spring AI</a>!</li></ul><p>Si l&#x27;on tient compte de tous ces éléments, Elasticsearch remplit toutes les conditions d&#x27;un excellent magasin de vecteurs, alors configurons-le et commençons à construire notre application !</p><h2>Prise en main d&#x27;Elasticsearch</h2><p>Nous allons avoir besoin d&#x27;Elasticsearch et de Kibana, la console d&#x27;interface utilisateur que vous utiliserez pour interagir avec les données hébergées dans la base de données.</p><p>Vous pouvez tout essayer sur votre machine locale grâce aux images Docker et à la <a href="http://elastic.co">page d&#x27;accueil d&#x27;Elastic.co.</a> Allez-y, faites défiler vers le bas pour trouver la commande <code>curl</code>, exécutez-la et envoyez-la directement dans votre shell :</p><pre><code> curl -fsSL https://elastic.co/start-local | sh 
  ______ _           _   _      
 |  ____| |         | | (_)     
 | |__  | | __ _ ___| |_ _  ___ 
 |  __| | |/ _` / __| __| |/ __|
 | |____| | (_| \__ \ |_| | (__ 
 |______|_|\__,_|___/\__|_|\___|
-------------------------------------------------
🚀 Run Elasticsearch and Kibana for local testing
-------------------------------------------------
ℹ️  Do not use this script in a production environment
⌛️ Setting up Elasticsearch and Kibana v9.0.0...
- Generated random passwords
- Created the elastic-start-local folder containing the files:
  - .env, with settings
  - docker-compose.yml, for Docker services
  - start/stop/uninstall commands
- Running docker compose up --wait
[+] Running 25/26
 ✔ kibana_settings Pulled                                                 16.7s 
 ✔ kibana Pulled                                                          26.8s 
 ✔ elasticsearch Pulled                                                   17.4s                                                                     
[+] Running 6/6
 ✔ Network elastic-start-local_default             Created                 0.0s 
 ✔ Volume "elastic-start-local_dev-elasticsearch"  Created                 0.0s 
 ✔ Volume "elastic-start-local_dev-kibana"         Created                 0.0s 
 ✔ Container es-local-dev                          Healthy                12.9s 
 ✔ Container kibana_settings                       Exited                 11.9s 
 ✔ Container kibana-local-dev                      Healthy                21.8s 
🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: w1GB15uQ
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: SERqaGlKWUJLNVJDODc1UGxjLWE6WFdxSTNvMU5SbVc5NDlKMEhpMzJmZw==
Learn more at https://github.com/elastic/start-local
➜  ~ </code></pre><p>Il suffit de tirer et de configurer des images Docker pour Elasticsearch et Kibana, et après quelques minutes, elles seront opérationnelles sur votre machine locale, avec les informations d&#x27;identification de connexion.</p><p>Vous avez également deux urls différentes que vous pouvez utiliser pour interagir avec votre instance Elasticsearch. Suivez les instructions et dirigez votre navigateur vers <a href="http://localhost:5601">http://localhost:5601</a>.</p><p>Notez également le nom d&#x27;utilisateur <code>elastic</code> et le mot de passe affichés sur la console : vous en aurez besoin pour vous connecter (dans l&#x27;exemple ci-dessus, il s&#x27;agit respectivement de <code>elastic</code> et <code>w1GB15uQ</code>).</p><p></p><h2>L&#x27;intégration de l&#x27;application</h2><p>Allez sur la page <a href="https://start.spring.io">Spring Initializr</a> et générez un nouveau projet Spring AI avec les dépendances suivantes :</p><ul><li><code>Elasticsearch Vector Store</code></li><li><code>Spring Boot Actuator</code></li><li><code>GraalVM</code></li><li><code>OpenAI</code></li><li><code>Web</code></li></ul><p>Veillez à choisir la dernière et la meilleure version de Java (idéalement Java 24 - au moment de la rédaction de ce document - ou une version plus récente) et l&#x27;outil de construction de votre choix. Nous utilisons Apache Maven dans cet exemple.</p><p>Cliquez sur <code>Generate</code>, puis décompressez le projet et importez-le dans l&#x27;IDE de votre choix. (Nous utilisons IntelliJ IDEA.)</p><p>Commençons par le commencement : spécifions les détails de la connexion pour votre application Spring Boot. Sur <code>application.properties,</code>, écrivez ce qui suit :</p><pre><code>spring.elasticsearch.uris=http://localhost:9200
spring.elasticsearch.username=elastic
spring.elasticsearch.password=w1GB15uQ</code></pre><p>Nous allons également utiliser la capacité de stockage vectoriel de Spring AI pour initialiser tout ce qui est nécessaire du côté d&#x27;Elasticsearch en termes de structures de données, donc spécifier :</p><pre><code>spring.ai.vectorstore.elasticsearch.initialize-schema=true</code></pre><p>Nous allons utiliser <strong>OpenAI</strong> dans cette démo, en particulier le <strong>modèle d&#x27;intégration</strong> et le <strong>modèle de conversation </strong>(n&#x27;hésitez pas à utiliser le service que vous préférez, tant que <a href="https://docs.spring.io/spring-ai/reference/api/embeddings.html#available-implementations">Spring AI le prend en charge</a>).</p><p>Le modèle d&#x27;intégration est nécessaire pour créer des intégrations des données avant de les stocker dans Elasticsearch. Pour qu&#x27;OpenAI fonctionne, nous devons spécifier l&#x27;adresse <code>API key</code>:</p><pre><code>spring.ai.openai.api-key=...</code></pre><p>Vous pouvez la définir comme une variable d&#x27;environnement telle que <code>SPRING_AI_OPENAI_API_KEY</code> pour éviter d&#x27;avoir à la stocker dans votre code source.</p><p>Nous allons télécharger des fichiers, donc assurez-vous de personnaliser la quantité de données pouvant être téléchargées vers le conteneur de servlet :</p><pre><code>spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB</code></pre><p>Nous y sommes presque ! Avant de nous plonger dans l&#x27;écriture du code, donnons un aperçu de la manière dont cela va fonctionner.</p><p>Sur notre machine, nous avons téléchargé le <a href="https://images-cdn.fantasyflightgames.com/filer_public/9f/aa/9faa23a3-9f71-4c77-865f-bba4aac8a258/runewars-revised-_rulebook.pdf">fichier suivant</a> (une liste de règles pour un jeu de société), l&#x27;avons renommé en <code>test.pdf</code> et l&#x27;avons placé dans <code>~/Downloads/test.pdf</code>.</p><p>Le fichier sera envoyé au point de terminaison <code>/rag/ingest</code> (remplacez le chemin d&#x27;accès en fonction de votre configuration locale) :</p><pre><code>http --form POST http://localhost:8080/rag/ingest path@/Users/jlong/Downloads/test.pdf</code></pre><p>Cela peut prendre quelques secondes...</p><p>En coulisses, les données sont envoyées à OpenAI, qui crée des enchâssements de données ; ces données sont ensuite écrites dans Elasticsearch, à la fois les vecteurs et le texte d&#x27;origine.</p><p>C&#x27;est à partir de ces données, et de tous les enchâssements qu&#x27;elles contiennent, que la magie opère. Nous pouvons ensuite interroger Elasticsearch à l&#x27;aide de l&#x27;interface <code>VectorStore</code>.</p><p>Le flux complet se présente comme suit :</p><ul><li>Le client HTTP télécharge le PDF de votre choix vers l&#x27;application Spring.</li><li>Spring AI se charge de l&#x27;extraction du texte de notre PDF et découpe chaque page en morceaux de 800 caractères.</li><li>OpenAI génère la représentation vectorielle pour chaque morceau.</li><li>Le texte fragmenté et l&#x27;intégration sont ensuite stockés dans Elasticsearch.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9734adb2d7128e61c515d5855dfad6d3a326a4a1-1454x706.png" style="width: 100%;" /><p>Enfin, nous émettrons une requête :</p><pre><code>http :8080/rag/query question=="where do you place the reward card after obtaining it?" </code></pre><p>Et nous obtiendrons une réponse pertinente :</p><pre><code>After obtaining a Reward card, you place it facedown under the Hero card of the hero who received it.
Found at page: 28 of the manual</code></pre><p>C&#x27;est bien ! Comment cela fonctionne-t-il ?</p><ul><li>Le client HTTP soumet la question à l&#x27;application Spring.</li><li>Spring AI obtient la représentation vectorielle de la question auprès d&#x27;OpenAI.</li><li>Cette intégration permet de rechercher des documents similaires dans les morceaux Elasticsearch stockés et d&#x27;extraire les documents les plus similaires.</li><li>Spring AI envoie ensuite la question et le contexte récupéré à OpenAI pour générer une réponse LLM.</li><li>Enfin, il renvoie la réponse générée et une référence au contexte récupéré.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3799de6e8cb13ce49b9e136cfe593263030231a8-1464x1050.png" style="width: 100%;" /><p>Plongeons dans le code Java pour voir comment cela fonctionne réellement.</p><p>Tout d&#x27;abord, la classe <strong>Main</strong>: il s&#x27;agit d&#x27;une classe principale standard pour n&#x27;importe quelle application Spring Boot.</p><pre><code>@SpringBootApplication
public class DemoApplication {
 	public static void main(String[] args) { 
     		SpringApplication.run(DemoApplication.class, args);
 	}
}</code></pre><p>Il n&#x27;y a rien à voir. Continuons...</p><p>Ensuite, un contrôleur HTTP de base :</p><pre><code>@RestController
class RagController {

   private final RagService rag;

   RagController(RagService rag) {
       this.rag = rag;
   }

   @PostMapping("/rag/ingest")
   ResponseEntity<?> ingestPDF(@RequestBody MultipartFile path) {
       rag.ingest(path.getResource());
       return ResponseEntity.ok().body("Done!");
   }

   @GetMapping("/rag/query")
   ResponseEntity<?> query(@RequestParam String question) {
       String response = rag.directRag(question);
       return ResponseEntity.ok().body(response);
   }
}</code></pre><p>Le contrôleur appelle simplement un service que nous avons construit pour gérer l&#x27;ingestion de fichiers et leur écriture dans la base de données vectorielles Elasticsearch, puis pour faciliter les requêtes sur cette même base de données vectorielles.</p><p>Examinons le service :</p><pre><code>@Service
class RagService {

   private final ElasticsearchVectorStore vectorStore;

   private final ChatClient ai;

   RagService(ElasticsearchVectorStore vectorStore, ChatClient.Builder clientBuilder) {
       this.vectorStore = vectorStore;
       this.ai = clientBuilder.build();
   }

   void ingest(Resource path) {
       PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(path);
       List<Document> batch = new TokenTextSplitter().apply(pdfReader.read());
       vectorStore.add(batch);
   }

  // TBD
}</code></pre><p>Ce code gère l&#x27;ensemble de l&#x27;ingestion : étant donné un Spring Framework <code>Resource</code>, qui est un conteneur d&#x27;octets, nous lisons les données PDF (présumées être un fichier <code>.PDF</code> - assurez-vous de le valider avant d&#x27;accepter des entrées arbitraires !) à l&#x27;aide de Spring AI <code>PagePdfDocumentReader</code>, puis nous les tokenisons à l&#x27;aide de Spring AI <code>TokenTextSplitter</code>, et enfin nous ajoutons les <code>List&lt;Document&gt;</code>s résultants à l&#x27;implémentation de <code>VectorStore</code>, <code>ElasticsearchVectorStore</code>.</p><p>Vous pouvez le confirmer en utilisant Kibana : après avoir envoyé un fichier au point de terminaison <code>/rag/ingest</code>, ouvrez votre navigateur sur <code>localhost:5601</code> et, dans le menu latéral de gauche, naviguez vers <code>Dev Tools</code>. Vous pouvez y émettre des requêtes pour interagir avec les données de l&#x27;instance Elasticsearch.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c85e522f02f8b2da7462cd428dc7e952c9692542-1600x1040.png" style="width: 100%;" /><p>Emettez une requête comme celle-ci :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/00974a176cbce11e70fcab24fb4b3f9c6e205982-1600x1040.png" style="width: 100%;" /><p>Passons maintenant aux choses sérieuses : comment récupérer ces données en réponse à des requêtes d&#x27;utilisateurs ?</p><p>Voici un premier aperçu de la mise en œuvre de la requête, dans une méthode appelée <code>directRag</code>.</p><pre><code>String directRag(String question) {
   // Query the vector store for documents related to the question
   List<Document> vectorStoreResult =
           vectorStore.doSimilaritySearch(SearchRequest.builder().query(question).topK(5)
                   .similarityThreshold(0.7).build());

   // Merging the documents into a single string
   String documents = vectorStoreResult.stream()
           .map(Document::getText)
           .collect(Collectors.joining(System.lineSeparator()));

   // Exit if the vector search didn't find any results
   if (documents.isEmpty()) {
       return "No relevant context found. Please change your question.";
   }

   // Setting the prompt with the context
   String prompt = """
           You're assisting with providing the rules of the tabletop game Runewars.
           Use the information from the DOCUMENTS section to provide accurate answers to the
           question in the QUESTION section.
           If unsure, simply state that you don't know.
          
           DOCUMENTS:
           """ + documents
           + """
           QUESTION:
           """ + question;


   // Calling the chat model with the question
   String response = ai
           .prompt()
           .user(prompt)
           .call()
           .content();

   return response +
           System.lineSeparator() +
           "Found at page: " +
           // Retrieving the first ranked page number from the document metadata
           vectorStoreResult.getFirst().getMetadata().get(PagePdfDocumentReader.METADATA_START_PAGE_NUMBER) +
           " of the manual";

}</code></pre><p>Le code est assez simple, mais nous allons le décomposer en plusieurs étapes :</p><ol><li>Utilisez le site <code>VectorStore</code> pour effectuer une recherche de similitude.</li><li>Étant donné tous les résultats, récupérer les <code>Document</code>s sous-jacents de Spring AI et extraire leur texte, en les concaténant tous en un seul résultat.</li><li>Envoyer les résultats du site <code>VectorStore</code> au modèle, accompagnés d&#x27;une invite indiquant au modèle ce qu&#x27;il doit en faire et de la question de l&#x27;utilisateur. Attendez la réponse et renvoyez-la.</li></ol><p></p><p>Il s&#x27;agit de la <strong>RAG</strong> (retrieval augmented generation). Il s&#x27;agit de l&#x27;idée que nous utilisons des données provenant d&#x27;un magasin de vecteurs pour informer le traitement et l&#x27;analyse effectués par le modèle. Maintenant que vous savez comment faire, espérons que vous n&#x27;aurez jamais à le faire ! En tout cas, pas de cette manière : Les <a href="https://docs.spring.io/spring-ai/reference/api/advisors.html">conseillers de</a> Spring AI sont là pour simplifier encore davantage ce processus.</p><p>Advisors vous permet de pré- et post-traiter une requête vers un modèle donné, en plus de fournir une couche d&#x27;abstraction entre votre application et le magasin de vecteurs. Ajoutez la dépendance suivante à votre construction :<br/></p><pre><code><dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency></code></pre><p>Ajoutez une autre méthode appelée <code>advisedRag(String question)</code> à la classe :</p><pre><code>String advisedRag(String question) {
   return this.ai
           .prompt()
           .user(question)
           .advisors(new QuestionAnswerAdvisor(vectorStore))
           .call()
           .content();
}</code></pre><p>Toute la logique du modèle RAG est encapsulée dans le site <code>QuestionAnswerAdvisor</code>. Pour le reste, tout se passe comme n&#x27;importe quelle demande adressée à <code>ChatModel</code>! C&#x27;est bien !</p><p>Vous pouvez <a href="https://github.com/xeraa/rag-with-java-springai-elasticsearch">obtenir le code complet sur GitHub</a>.</p><h2>Conclusion</h2><p>Dans cette démo, nous avons utilisé des images Docker et tout fait sur notre machine locale, mais l&#x27;objectif ici est de construire des systèmes et des services d&#x27;IA dignes d&#x27;une production. Il y a plusieurs choses que vous pouvez faire pour que cela devienne une réalité.</p><p>Tout d&#x27;abord, vous pouvez ajouter <a href="https://docs.spring.io/spring-boot/reference/actuator/index.html#actuator">Spring Boot Actuator</a> pour surveiller la consommation de jetons. Les jetons sont une approximation du coût de la complexité (et parfois du coût en dollars) d&#x27;une demande donnée au modèle.</p><p>Vous avez déjà l&#x27;Actuator Spring Boot sur le classpath, il suffit donc de spécifier les propriétés suivantes pour afficher toutes les métriques (capturées par le magnifique projet <a href="http://micrometer.io">Micrometer.io</a> ) :</p><pre><code>management.endpoints.web.exposure.include=*</code></pre><p>Redémarrez votre application. Effectuez une recherche, puis rendez-vous sur <a href="http://localhost:8080/actuator/metrics">:</a> http://localhost:8080/actuator/metrics. Recherchez &quot;<code>token</code>&quot; et vous verrez des informations sur les jetons utilisés par l&#x27;application. Veillez à garder un œil sur ce point. Vous pouvez bien sûr utiliser <a href="https://docs.micrometer.io/micrometer/reference/implementations/elastic.html">l&#x27; intégration de Micrometer pour Elasticsearch</a> pour pousser ces métriques et faire en sorte qu&#x27;Elasticsearch agisse comme votre base de données de séries temporelles de choix, aussi !</p><p>Il faut alors considérer que chaque fois que nous faisons une requête à un entrepôt de données comme Elasticsearch, ou à OpenAI, ou à d&#x27;autres services réseau, nous faisons de l&#x27;IO et - souvent - cette IO bloque les threads sur lesquels elle s&#x27;exécute. Java 21 et les versions ultérieures intègrent des <strong>threads virtuels</strong> non bloquants qui améliorent considérablement l&#x27;évolutivité. L&#x27;activer avec :<br/></p><pre><code>spring.threads.virtual.enabled=true</code></pre><p>Enfin, vous voudrez héberger votre application et vos données dans un endroit où elles pourront prospérer et évoluer. Nous sommes certains que vous avez déjà réfléchi à l&#x27;endroit où vous allez exécuter votre application, mais où allez-vous héberger vos données ? Pouvons-nous vous recommander l&#x27;<a href="https://cloud.elastic.co/">Elastic Cloud</a>? Il est sécurisé, privé, évolutif et doté de nombreuses fonctionnalités. Ce que nous préférons ? Si vous le souhaitez, vous pouvez obtenir l&#x27;édition Serverless dans laquelle Elastic porte le pager, pas vous !</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch en JavaScript dans les règles de l'art, partie II]]></title>
            <description><![CDATA[Découvrez les bonnes pratiques en production et comment exécuter le client Elasticsearch Node.js dans des environnements serverless pour réduire les erreurs de codage. ]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/how-to-use-elasticsearch-in-javascript-part-ii</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/how-to-use-elasticsearch-in-javascript-part-ii</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Voici la deuxième partie de notre série Elasticsearch en JavaScript. Dans la<a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i"> première partie,</a> nous avons appris à mettre en place notre environnement correctement, à configurer le client Node.js, à indexer les données et à effectuer des recherches. Dans cette deuxième partie, nous allons apprendre à mettre en œuvre les meilleures pratiques de production et à exécuter le client Elasticsearch <a href="http://node.js">Node.js</a> dans des environnements Serverless.</p><p>Nous ferons le point :</p><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#production-best-practices">Meilleures pratiques de production</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#error-handling">Gestion des erreurs</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#testing">Tests</a></li></ul></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#serverless-environments">Environnements sans serveur</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#running-the-client-on-elastic-serverless">Exécuter le client sur Elastic Serverless</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#running-the-client-on-function-as-a-service-environment">Exécution du client dans un environnement de services fonctionnels (function-as-a-service)</a></li></ul></li></ul><p><em>Vous pouvez consulter le code source avec les exemples <strong><a href="https://github.com/Delacrobix/JS-client-best-practices_article">ici</a>.</strong></em></p><h2>Meilleures pratiques de production</h2><h3>Gestion des erreurs dans Elasticsearch</h3><p>Une caractéristique utile du client Elasticsearch dans Node.js est qu&#x27;il expose des objets pour les erreurs possibles dans Elasticsearch afin que vous puissiez les valider et les traiter de différentes manières.</p><p>Pour <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript/connecting#client-error-handling">les voir tous</a>, cliquez ici : </p><pre><code>const { errors } = require('@elastic/elasticsearch')
console.log(errors)</code></pre><p>Revenons à l&#x27;exemple de la recherche et traitons certaines des erreurs possibles :</p><pre><code>app.get("/search/lexic", async (req, res) => {
 ....
  } catch (error) {
    if (error instanceof errors.ResponseError) {
      let errorMessage =
        "Response error!, query malformed or server down, contact the administrator!";

      if (error.body.error.type === "parsing_exception") {
        errorMessage = "Query malformed, make sure mappings are set correctly";
      }

      res.status(error.meta.statusCode).json({
        erroStatus: error.meta.statusCode,
        success: false,
        results: null,
        error: errorMessage,
      });
    }

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p><code>ResponseError</code> en particulier, se produit lorsque la réponse est <code>4xx</code> ou <code>5xx</code>, ce qui signifie que la demande est incorrecte ou que le serveur n&#x27;est pas disponible.</p><p>Nous pouvons tester ce type d&#x27;erreur en générant des requêtes erronées, par exemple en essayant d&#x27;<strong>effectuer une recherche de terme sur un champ de type texte :</strong></p><p>Erreur par défaut :</p><pre><code> {
    "success": false,
    "results": null,
    "error": "parsing_exception\n\tRoot causes:\n\t\tparsing_exception: [terms] query does not support [visit_details]"
}</code></pre><p>Erreur personnalisée : </p><pre><code>{
    "erroStatus": 400,
    "success": false,
    "results": null,
    "error": "Response error!, query malformed or server down; contact the administrator!"
}</code></pre><p>Nous pouvons également capturer et traiter chaque type d&#x27;erreur d&#x27;une certaine manière. Par exemple, nous pouvons ajouter une logique de réessai dans un site <code>TimeoutError</code>.</p><pre><code>app.get("/search/semantic", async (req, res) => {
    try {
  ...
  } catch (error) {
    if (error instanceof errors.TimeoutError) {


     // Retry logic...

      res.status(error.meta.statusCode).json({
        erroStatus: error.meta.statusCode,
        success: false,
        results: null,
        error:
          "The request took more than 10s after 3 retries. Try again later.",
      });
    }
  }
});</code></pre><h3>Tests</h3><p>Les tests sont essentiels pour garantir la stabilité de l&#x27;application. Pour tester le code d&#x27;une manière isolée d&#x27;Elasticsearch, nous pouvons utiliser la bibliothèque <a href="https://github.com/elastic/elasticsearch-js-mock">elasticsearch-js-mock</a> lors de la création de notre cluster.</p><p>Cette bibliothèque nous permet d&#x27;instancier un client qui est très similaire au vrai client mais qui répondra à notre configuration en remplaçant seulement la couche HTTP du client par une couche fictive tout en gardant le reste identique à l&#x27;original.</p><p>Nous installerons la bibliothèque mocks et <a href="https://github.com/avajs/ava">AVA</a> pour les tests automatisés.</p><p><code>npm install @elastic/elasticsearch-mock</code></p><p><code>npm install --save-dev ava</code></p><p>Nous allons configurer le fichier <code>package.json</code> pour exécuter les tests. Veillez à ce qu&#x27;il en soit ainsi :</p><pre><code>"type": "module",
	"scripts": {
		"test": "ava"
	},
	"devDependencies": {
		"ava": "^5.0.0"
	}</code></pre><p>Créons maintenant un fichier <code>test.js</code> et installons notre client fictif :</p><pre><code>const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')

const mock = new Mock()
const client = new Client({
  node: 'http://localhost:9200',
  Connection: mock.getConnection()
})</code></pre><p>Maintenant, ajoutez un simulacre de recherche sémantique :</p><pre><code>function createSemanticSearchMock(query, indexName) {
  mock.add(
    {
      method: "POST",
      path: `/${indexName}/_search`,
      body: {
        query: {
          semantic: {
            field: "semantic_field",
            query: query,
          },
        },
      },
    },
    () => {
      return {
        hits: {
          total: { value: 2, relation: "eq" },
          hits: [
            {
              _id: "1",
              _score: 0.9,
              _source: {
                owner_name: "Alice Johnson",
                pet_name: "Buddy",
                species: "Dog",
                breed: "Golden Retriever",
                vaccination_history: ["Rabies", "Parvovirus", "Distemper"],
                visit_details:
                  "Annual check-up and nail trimming. Healthy and active.",
              },
            },
            {
              _id: "2",
              _score: 0.7,
              _source: {
                owner_name: "Daniel Kim",
                pet_name: "Mochi",
                species: "Rabbit",
                breed: "Mixed",
                vaccination_history: [],
                visit_details:
                  "Nail trimming and general health check. No issues.",
              },
            },
          ],
        },
      };
    }
  );
}</code></pre><p>Nous pouvons maintenant créer un test pour notre code, en nous assurant que la partie Elasticsearch renvoie toujours les mêmes résultats :</p><pre><code>import test from 'ava';

test("performSemanticSearch must return formatted results correctly", async (t) => {
  const indexName = "vet-visits";
  const query = "Which pets had nail trimming?";

  createSemanticSearchMock(query, indexName);

  async function performSemanticSearch(esClient, q, indexName = "vet-visits") {
    try {
      const result = await esClient.search({
        index: indexName,
        body: {
          query: {
            semantic: {
              field: "semantic_field",
              query: q,
            },
          },
        },
      });

      return {
        success: true,
        results: result.hits.hits,
      };
    } catch (error) {
      if (error instanceof errors.TimeoutError) {
        return {
          success: false,
          results: null,
          error: error.body.error.reason,
        };
      }

      return {
        success: false,
        results: null,
        error: error.message,
      };
    }
  }

  const result = await performSemanticSearch(esClient, query, indexName);

  t.true(result.success, "The search must be successful");
  t.true(Array.isArray(result.results), "The results must be an array");

  if (result.results.length > 0) {
    t.true(
      "_source" in result.results[0],
      "Each result must have a _source property"
    );
    t.true(
      "pet_name" in result.results[0]._source,
      "Results must include the pet_name field"
    );
    t.true(
      "visit_details" in result.results[0]._source,
      "Results must include the visit_details field"
    );
  }
});</code></pre><p>Exécutons les tests.</p><p><code>npm run test</code></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42feae845ae8eae03c37ad7ad114e8db35984812-1186x302.png" style="width: 100%;" /><p>C&#x27;est fait ! Désormais, nous pouvons tester notre application en nous concentrant à 100 % sur le code et non sur des facteurs externes.</p><h2>Environnements sans serveur</h2><h3>Exécution du client avec Elastic Serverless</h3><p>Nous avons abordé l&#x27;exécution d&#x27;Elasticsearch sur le Cloud ou sur site ; cependant, le client Node.js prend également en charge les connexions à <a href="https://www.elastic.co/guide/en/serverless/current/intro.html">Elastic Cloud Serverless</a>.</p><p>Elastic Cloud Serverless vous permet de créer un projet dans lequel vous n&#x27;avez pas besoin de vous préoccuper de l&#x27;infrastructure puisqu&#x27;Elastic s&#x27;en charge en interne, et vous n&#x27;avez qu&#x27;à vous préoccuper des données que vous souhaitez indexer et de la durée pendant laquelle vous souhaitez y avoir accès.</p><p>Du point de vue de l&#x27;utilisation, Serverless découple le calcul du stockage, offrant des fonctionnalités d&#x27;autoscaling pour la <a href="https://www.elastic.co/search-labs/blog/elasticsearch-serverless-tier-autoscaling">recherche</a> et l&#x27;<a href="https://www.elastic.co/search-labs/blog/elasticsearch-ingest-autoscaling">indexation</a>. Cela vous permet de n&#x27;augmenter que les ressources dont vous avez réellement besoin.</p><p>Le client effectue les adaptations suivantes pour se connecter à Serverless :</p><ul><li>Désactive le sniffing et ignore toutes les options liées au sniffing.</li><li>Ignore tous les nœuds passés dans la configuration sauf le premier, et ignore toutes les options de filtrage et de sélection des nœuds.</li><li>Active la compression et `TLSv1_2_method` (identique à la configuration pour Elastic Cloud)</li><li>Ajoute un en-tête HTTP `elastic-api-version` à toutes les requêtes</li><li>Utilise `CloudConnectionPool` par défaut au lieu de `WeightedConnectionPool`.</li><li>Désactive les en-têtes `content-type` et `accept` en faveur des types MIME standard.</li></ul><p>Pour connecter votre projet serverless, vous devez utiliser le paramètre serverMode : serverless.</p><pre><code>const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  node: 'ELASTICSEARCH_ENDPOINT',
  auth: { apiKey: 'ELASTICSEARCH_API_KEY' },
  serverMode: "serverless",
});</code></pre><h3>Exécution du client sur une plateforme FaaS (Function-as-a-Service)</h3><p>Dans l&#x27;exemple, nous avons utilisé un serveur Node.js, mais vous pouvez également vous connecter en utilisant un environnement de fonction en tant que service avec des fonctions telles que AWS lambda, GCP Run, etc.</p><pre><code>'use strict'

const { Client } = require('@elastic/elasticsearch')

const client = new Client({
  // client initialisation
})

exports.handler = async function (event, context) {
  // use the client
}</code></pre><p>Un autre exemple consiste à se connecter à des services comme Vercel, qui est également sans serveur. Vous pouvez consulter cet <a href="https://github.com/elastic/elasticsearch-js/blob/main/docs/examples/proxy/README.md">exemple complet</a> de la manière de procéder, mais la partie la plus pertinente du <a href="https://github.com/elastic/elasticsearch-js/blob/main/docs/examples/proxy/api/search.js">point de terminaison de la recherche</a> ressemble à ceci :</p><pre><code>const response = await client.search(
  {
    index: INDEX,
    // You could directly send from the browser
    // the Elasticsearch's query DSL, but it will
    // expose you to the risk that a malicious user
    // could overload your cluster by crafting
    // expensive queries.
    query: {
      match: { field: req.body.text },
    },
  },
  {
    headers: {
      Authorization: `ApiKey ${token}`,
    },
  }
);</code></pre><p>Ce point d&#x27;accès se trouve dans le dossier /api et est exécuté du côté du serveur, de sorte que le client ne contrôle que le paramètre &quot;text&quot; qui correspond au terme de la recherche.</p><p>L&#x27;utilisation de la fonction en tant que service implique que, contrairement à un serveur fonctionnant 24 heures sur 24 et 7 jours sur 7, les fonctions ne font appel qu&#x27;à la machine qui exécute la fonction et, une fois celle-ci terminée, la machine passe en mode repos afin de consommer moins de ressources.</p><p>Cette configuration peut être pratique si l&#x27;application ne reçoit pas trop de demandes ; dans le cas contraire, les coûts peuvent être élevés. Vous devez également tenir compte du <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html">cycle de vie des fonctions</a> et des durées d&#x27;exécution (qui ne peuvent être que de quelques secondes dans certains cas).</p><h2>Conclusion</h2><p>Dans cet article, nous avons appris à gérer les erreurs, ce qui est crucial dans les environnements de production. Nous avons également abordé le test de notre application en simulant le service Elasticsearch, ce qui permet d&#x27;obtenir des tests fiables quel que soit l&#x27;état du cluster et de se concentrer sur notre code.</p><p>Enfin, nous avons montré comment mettre en place une pile entièrement sans serveur en provisionnant à la fois Elastic Cloud Serverless et une application Vercel.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment optimiser l'espace disque et l'utilisation d'Elasticsearch ?]]></title>
            <description><![CDATA[Découvrez comment prévenir et gérer les cas où le disque Elasticsearch est trop plein (surutilisation) et ceux où sa capacité est sous-utilisée afin d'optimiser les coûts du cluster.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/optimize-elasticsearch-disk-space-and-usage</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/optimize-elasticsearch-disk-space-and-usage</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 16 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5d41a078dd03e4480a0ff4e9591c8618b9bab4d0-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La gestion des disques est importante pour toute base de données, et Elasticsearch ne fait pas exception. Si vous n&#x27;avez pas assez d&#x27;espace disque disponible, Elasticsearch arrêtera d&#x27;allouer des shards au nœud. Cela vous empêchera éventuellement d&#x27;écrire des données dans le cluster, avec le risque potentiel de perte de données dans votre application. En revanche, si vous disposez de trop d&#x27;espace disque, vous payez pour plus de ressources que vous n&#x27;en avez besoin.</p><h2>Historique des filigranes</h2><p>Il existe différents seuils &quot;en filigrane&quot; sur votre cluster Elasticsearch qui vous aident à suivre l&#x27;espace disque disponible. Lorsque le disque d&#x27;un nœud se remplit, le premier seuil à être franchi est le &quot;filigrane de disque faible&quot;. Le deuxième seuil sera alors le &quot;seuil de filigrane de disque élevé&quot;. Enfin, le &quot;stade de l&#x27;inondation du disque&quot; sera atteint. Une fois ce seuil dépassé, le cluster bloque l&#x27;écriture dans TOUS les index qui ont un shard (primaire ou réplique) sur le nœud qui a passé le filigrane. Les lectures (recherches) restent possibles.</p><h2>Comment prévenir et gérer les cas où le disque est trop plein (surutilisation) ?</h2><p>Il existe plusieurs méthodes pour gérer les cas où le disque Elasticsearch est trop plein :</p><ol><li><strong>Supprimer les</strong> <strong>anciennes données :</strong> En général, les données ne doivent pas être conservées indéfiniment. L&#x27;un des moyens de prévenir et de résoudre le problème des disques trop pleins est de veiller à ce que les données atteignant un certain âge soient archivées et supprimées de manière fiable. L&#x27;un des moyens d&#x27;y parvenir est d&#x27;utiliser l&#x27;<a href="https://www.elastic.co/docs/manage-data/lifecycle/index-lifecycle-management">ILM</a>.</li><li><strong>Augmenter la capacité de stockage :</strong> Si vous ne pouvez pas supprimer les données, vous pouvez ajouter des nœuds de données supplémentaires ou augmenter la taille des disques afin de conserver toutes les données sans nuire aux performances. Si vous devez ajouter de la capacité de stockage à la grappe, vous devez déterminer si vous devez ajouter uniquement de la capacité de stockage, ou à la fois de la capacité de stockage et des ressources RAM et CPU en proportion (voir la section sur le <a href="https://www.elastic.co/search-labs/blog/optimize-elasticsearch-disk-space-and-usage#the-relationship-between-disk-size,-ram-and-cpu">rapport entre la taille du disque, la RAM et le CPU</a> ci-dessous).</li></ol><h2>Comment ajouter de la capacité de stockage à votre cluster Elasticsearch ?</h2><ol><li><strong>Augmentez le nombre de nœuds de données : </strong>N&#x27;oubliez pas que les nouveaux nœuds doivent être de la même taille que les nœuds existants et de la même version d&#x27;Elasticsearch.</li><li><strong>Augmenter la taille des nœuds existants : </strong>Dans les environnements en nuage, il est généralement facile d&#x27;augmenter la taille du disque et la RAM/CPU sur les nœuds existants.</li><li><strong>Augmentez uniquement la taille du disque : </strong>Dans les environnements en nuage, il est souvent relativement facile d&#x27;augmenter la taille du disque.</li><li><a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore"><strong>Instantané</strong> <strong>et</strong> <strong>restauration</strong></a><strong>:</strong> Si vous souhaitez que les anciennes données soient récupérées sur demande dans le cadre d&#x27;un processus automatisé à partir des sauvegardes, vous pouvez prendre des clichés des anciens index, les supprimer et restaurer temporairement les données sur demande à partir des clichés. </li><li><strong>Réduire le nombre de répliques par groupe :</strong> Une autre option pour réduire les données consiste à réduire le nombre de répliques de chaque groupe. Pour des raisons de haute disponibilité, il est préférable d&#x27;avoir une réplique par bloc de données, mais lorsque les données vieillissent, il est possible de se passer de répliques. Cela peut généralement fonctionner si les données sont persistantes ou si vous disposez d&#x27;une sauvegarde à restaurer en cas de besoin.</li><li><strong>Créer des alertes :</strong> Afin d&#x27;éviter que les disques ne se remplissent à l&#x27;avenir et d&#x27;agir de manière proactive, vous devriez créer des alertes basées sur l&#x27;utilisation du disque qui vous préviendront lorsque le disque commencera à se remplir. </li></ol><h2>Comment prévenir et traiter les cas où la capacité du disque est sous-utilisée ?</h2><p>Si la capacité de votre disque est sous-utilisée, il existe plusieurs options pour réduire le volume de stockage de votre cluster.</p><h3>Comment réduire le volume de stockage d&#x27;un cluster Elasticsearch ?</h3><p>Il existe plusieurs méthodes pour réduire le volume de stockage d&#x27;un cluster.</p><p><strong>1. Réduire le nombre de nœuds de données</strong></p><p>Si vous souhaitez réduire le stockage des données et réduire les ressources RAM et CPU dans la même proportion, il s&#x27;agit de la stratégie la plus simple. Le déclassement des nœuds inutiles devrait permettre de réaliser les économies les plus importantes.</p><p>Avant de mettre le nœud hors service, vous devez.. :</p><ul><li>Assurez-vous que le nœud à mettre hors service n&#x27;est pas nécessaire en tant que nœud MASTER. Vous devez toujours avoir au moins trois nœuds ayant le rôle de nœud MASTER.</li><li>Migrer les blocs de données hors du nœud à mettre hors service.</li></ul><p><strong>2. Remplacer les nœuds existants par des nœuds plus petits</strong></p><p>Si vous ne pouvez pas réduire davantage le nombre de nœuds (en général, 3 est une configuration minimale), vous pouvez alors réduire la taille des nœuds existants. Il est conseillé de veiller à ce que tous les nœuds de données aient la même mémoire RAM et la même taille de disque, étant donné que l&#x27;équilibre des ensembles est basé sur le nombre d&#x27;ensembles par nœud.</p><p>La procédure serait la suivante :</p><ul><li>Ajouter de nouveaux nœuds plus petits à la grappe</li><li>Faire migrer les shards à l&#x27;écart des nœuds à déclasser</li><li>Arrêter les anciens nœuds</li></ul><p><strong>3. Réduire la taille des disques sur les nœuds</strong></p><p>Si vous souhaitez uniquement réduire la taille des disques sur les nœuds sans modifier la mémoire vive ou l&#x27;unité centrale de la grappe, vous pouvez réduire la taille des disques pour chaque nœud. La réduction de la taille du disque sur un nœud Elasticsearch n&#x27;est pas un processus trivial.</p><p>La manière la plus simple de le faire est généralement d&#x27;effectuer les opérations suivantes :</p><ul><li>Migrations de shards depuis le nœud</li><li>Arrêter le nœud</li><li>Monter un nouveau volume de données sur le nœud avec la taille appropriée</li><li>Copier toutes les données de l&#x27;ancien volume de disque vers le nouveau volume</li><li>Détacher l&#x27;ancien volume A</li><li>Démarrer le nœud et migrer les unités de stockage vers le nœud</li></ul><p>Pour ce faire, vous devez disposer d&#x27;une capacité suffisante sur les autres nœuds pour stocker temporairement les fragments supplémentaires du nœud au cours de ce processus. Dans de nombreux cas, le coût de la gestion de ce processus peut dépasser les économies potentielles en termes d&#x27;utilisation du disque. Pour cette raison, il peut être plus simple de remplacer le nœud par un nouveau nœud ayant la taille de disque souhaitée (voir &quot;Remplacer les nœuds existants par des nœuds plus petits&quot; ci-dessus).</p><p>Lorsque vous payez pour des ressources inutiles, il est évident que vous pouvez réduire les coûts en optimisant l&#x27;utilisation de vos ressources.</p><h2>La relation entre la taille du disque, la mémoire vive et le processeur</h2><p>Le rapport idéal entre la capacité des disques et la mémoire vive dans votre cluster dépend de votre cas d&#x27;utilisation particulier. C&#x27;est pourquoi, lorsque vous envisagez de modifier votre capacité de stockage, vous devez également vous demander si vos ratios actuels Disque/RAM/CPU sont bien équilibrés et si, par conséquent, vous devez également ajouter/réduire la RAM/CPU dans les mêmes proportions.</p><p>Les besoins en RAM et en CPU dépendent du volume de l&#x27;activité d&#x27;<a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-indexing/">indexation</a>, du nombre et du type de requêtes, ainsi que de la quantité de données recherchées et agrégées. Elle est souvent proportionnelle à la quantité de données stockées sur la grappe, et doit donc également être liée à la taille du disque.</p><p>Le rapport entre la capacité du disque et la mémoire vive peut varier en fonction du cas d&#x27;utilisation. Voir quelques exemples ici :</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Activité de l'indice</td><td style="border: 1px solid #ccc; padding: 4px;">Conservation</td><td style="border: 1px solid #ccc; padding: 4px;">Activité de recherche</td><td style="border: 1px solid #ccc; padding: 4px;">Capacité du disque</td><td style="border: 1px solid #ccc; padding: 4px;">RAM</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Application de recherche d'entreprise</td><td style="border: 1px solid #ccc; padding: 4px;">Ingestion modérée de billes</td><td style="border: 1px solid #ccc; padding: 4px;">Longues</td><td style="border: 1px solid #ccc; padding: 4px;">Lumière</td><td style="border: 1px solid #ccc; padding: 4px;">2TB</td><td style="border: 1px solid #ccc; padding: 4px;">32GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Surveillance des applications</td><td style="border: 1px solid #ccc; padding: 4px;">Ingestion intensive de bois</td><td style="border: 1px solid #ccc; padding: 4px;">Court</td><td style="border: 1px solid #ccc; padding: 4px;">Lumière</td><td style="border: 1px solid #ccc; padding: 4px;">1TB</td><td style="border: 1px solid #ccc; padding: 4px;">32GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Commerce électronique</td><td style="border: 1px solid #ccc; padding: 4px;">Indexation des données légères</td><td style="border: 1px solid #ccc; padding: 4px;">Indéfinie</td><td style="border: 1px solid #ccc; padding: 4px;">Lourd</td><td style="border: 1px solid #ccc; padding: 4px;">500GB</td><td style="border: 1px solid #ccc; padding: 4px;">32GB</td>
                    </tr>
              </tbody>
            </table>
          <p><em>N&#x27;oubliez pas que la modification de la configuration des machines des nœuds doit être effectuée avec précaution, car elle peut entraîner l&#x27;arrêt du nœud et vous devez veiller à ce que les fragments ne commencent pas à migrer vers vos autres nœuds déjà surchargés.</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch en JavaScript dans les règles de l'art, partie I]]></title>
            <description><![CDATA[Expliquer comment créer un backend Elasticsearch prêt pour la production en JavaScript.  

Découvrez comment utiliser Elasticsearch avec JavaScript pour créer un serveur avec différents points de terminaison de recherche afin d’interroger les documents Elasticsearch en suivant les bonnes pratiques client/serveur.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/how-to-use-elasticsearch-in-javascript-part-i</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/how-to-use-elasticsearch-in-javascript-part-i</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/72200540ed258779d87e53a72ea189f8a138540c-1600x901.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Cet article est le premier d&#x27;une série qui traite de l&#x27;utilisation d&#x27;Elasticsearch avec JavaScript. Dans cette série, vous apprendrez les bases de l&#x27;utilisation d&#x27;Elasticsearch dans un environnement JavaScript et passerez en revue les fonctionnalités les plus pertinentes et les meilleures pratiques pour créer une application de recherche. À la fin, vous saurez tout ce dont vous avez besoin pour exécuter Elasticsearch à l&#x27;aide de JavaScript.</p><p>Dans cette première partie, nous passerons en revue</p><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#environment">Environnement</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#frontend,-backend,-or-serverless?">Frontend, backend ou serverless ?</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#connecting-the-client">Connexion du client</a></li></ul></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#indexing-documents">Indexation des documents</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#elasticsearch-client">Client Elasticsearch</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#semantic-mappings">Correspondances sémantiques</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#bulk-helper">Aide en vrac</a></li></ul></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#searching-data">Recherche de données</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#lexical-query-(/search/lexic?q=%3Cquery-term%3E)">Requête lexicale</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#semantic-query-(/search/semantic?q=%3Cquery-term%3E)">Requête sémantique</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#hybrid-query-(/search/hybrid?q=%3Cquery-term%3E)">Requête hybride</a></li></ul></li></ul><p><em>Vous pouvez consulter le code source avec les exemples <strong><a href="https://github.com/Delacrobix/JS-client-best-practices_article">ici</a>.</strong></em></p><h3>Qu&#x27;est-ce que le client Elasticsearch Node.js ?</h3><p>Le <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html">client Elasticsearch Node.js</a> est une bibliothèque JavaScript qui transpose les appels HTTP REST de l&#x27;API Elasticsearch en JavaScript. Il est ainsi plus facile à manipuler et dispose d&#x27;assistants qui simplifient les tâches telles que l&#x27;indexation de documents par lots.</p><h2>Environnement</h2><h3>Frontend, backend ou serverless ?</h3><p>Pour créer notre application de recherche à l&#x27;aide du client JavaScript, nous avons besoin d&#x27;au moins deux composants : un cluster Elasticsearch et un moteur d&#x27;exécution JavaScript pour exécuter le client.</p><p>Le client JavaScript prend en charge toutes les solutions Elasticsearch (Cloud, on-prem et Serverless), et il n&#x27;y a pas de différences majeures entre elles puisque le client gère toutes les variations en interne, vous n&#x27;avez donc pas à vous soucier de savoir laquelle utiliser.</p><p>Le moteur d&#x27;exécution JavaScript doit toutefois être exécuté à partir du <strong>serveur</strong> et <strong>non directement à partir du navigateur.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/92ce6cfd923c8008fa44f617a58193642d9d5879-661x410.png" style="width: 100%;" /><p>En effet, en appelant Elasticsearch depuis le navigateur, l&#x27;utilisateur peut obtenir des informations sensibles telles que la clé API du cluster, l&#x27;hôte ou la requête elle-même. Elasticsearch recommande de <strong>ne jamais exposer le cluster directement à l&#x27;internet </strong>et d&#x27;utiliser une couche intermédiaire qui abstrait toutes ces informations de sorte que l&#x27;utilisateur ne puisse voir que les paramètres. Pour en savoir plus sur ce sujet <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/es-security-principles.html#security-protect-cluster-traffic">, cliquez ici.</a></p><p>Nous suggérons d&#x27;utiliser un schéma comme celui-ci :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a08769f08ec73fe57bf2e961cfdfbb1cdd57919d-972x429.png" style="width: 100%;" /><p>Dans ce cas, le client n&#x27;envoie que les termes de recherche et une clé d&#x27;authentification pour votre serveur, tandis que votre serveur contrôle totalement la requête et la communication avec Elasticsearch.</p><h3>Connexion du client</h3><p>Commencez par créer une clé API en suivant <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">ces étapes.</a></p><p>En suivant l&#x27;exemple précédent, nous allons créer un simple serveur Express, et nous y connecter en utilisant un client depuis un serveur Node.JS.</p><p>Nous allons initialiser le projet avec NPM et installer le client Elasticsearch et <a href="https://expressjs.com/">Express.</a> Cette dernière est une bibliothèque qui permet d&#x27;activer des serveurs dans Node.js. En utilisant Express, nous pouvons interagir avec notre backend via HTTP.</p><p>Initialisons le projet :</p><p><code>npm init -y</code></p><p>Installer les dépendances :</p><p><code>npm install @elastic/elasticsearch express split2 dotenv</code></p><p>Laissez-moi vous expliquer :</p><ul><li><a href="https://www.npmjs.com/package/@elastic/elasticsearch"><strong><em>@elastic/elasticsearch</em></strong></a>: C&#x27;est le client officiel Node.js</li><li><a href="https://www.npmjs.com/package/express"><strong><em>express</em></strong></a>: Il nous permettra de faire tourner un serveur nodejs léger pour exposer Elasticsearch.</li><li><a href="https://www.npmjs.com/package/split2"><strong><em>split2</em></strong></a>: divise les lignes de texte en un flux. Utile pour traiter nos fichiers ndjson une ligne à la fois</li><li><a href="https://www.npmjs.com/package/dotenv"><strong><em>dotenv</em></strong></a>: Permet de gérer les variables d&#x27;environnement à l&#x27;aide d&#x27;un fichier .env fichier</li></ul><p>Créer un fichier .env à la racine du projet et ajoutez les lignes suivantes :</p><pre><code>ELASTICSEARCH_ENDPOINT="Your Elasticsearch endpoint"
ELASTICSEARCH_API_KEY="Your Elasticssearch API"</code></pre><p>Ainsi, nous pouvons importer ces variables à l&#x27;aide du paquetage <code>dotenv</code>.</p><p>Créer un fichier <code>server.js</code>:</p><pre><code>const express = require("express");
const bodyParser = require("body-parser");
const { Client } = require("@elastic/elasticsearch");
 
require("dotenv").config(); //environment variables setup

const ELASTICSEARCH_ENDPOINT = process.env.ELASTICSEARCH_ENDPOINT;
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY;
const PORT = 3000;


const app = express();

app.listen(PORT, () => {
  console.log("Server running on port", PORT);
});
app.use(bodyParser.json());


let esClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { apiKey: ELASTICSEARCH_API_KEY },  
});

app.get("/ping", async (req, res) => {
  try {
    const result = await esClient.info();

    res.status(200).json({
      success: true,
      clusterInfo: result,
    });
  } catch (error) {
    console.error("Error getting Elasticsearch info:", error);

    res.status(500).json({
      success: false,
      clusterInfo: null,
      error: error.message,
    });
  }
});</code></pre><p>Ce code met en place un serveur Express.js de base qui écoute sur le port 3000 et se connecte à un cluster Elasticsearch en utilisant une clé API pour l&#x27;authentification. Il comprend un point d&#x27;extrémité /ping qui, lorsqu&#x27;on y accède par une requête GET, interroge le cluster Elasticsearch pour obtenir des informations de base à l&#x27;aide de la méthode <code>.info()</code> du client Elasticsearch. </p><p>Si la requête aboutit, elle renvoie les informations sur le cluster au format JSON ; dans le cas contraire, elle renvoie un message d&#x27;erreur. Le serveur utilise également un intergiciel d&#x27;analyseur de corps pour traiter les corps de requête JSON.</p><p>Exécutez le fichier pour lancer le serveur :</p><p><code>node server.js</code></p><p>La réponse devrait ressembler à ceci :</p><pre><code>Server running on port 3000</code></pre><p>Et maintenant, consultons le point de terminaison <code>/ping</code> pour vérifier l&#x27;état de notre cluster Elasticsearch.</p><pre><code>curl http://localhost:3000/ping
{
    "success": true,
    "clusterInfo": {
        "name": "instance-0000000000",
        "cluster_name": "61b7e19eec204d59855f5e019acd2689",
        "cluster_uuid": "BIfvfLM0RJWRK_bDCY5ldg",
        "version": {
            "number": "9.0.0",
            "build_flavor": "default",
            "build_type": "docker",
            "build_hash": "112859b85d50de2a7e63f73c8fc70b99eea24291",
            "build_date": "2025-04-08T15:13:46.049795831Z",
            "build_snapshot": false,
            "lucene_version": "10.1.0",
            "minimum_wire_compatibility_version": "8.18.0",
            "minimum_index_compatibility_version": "8.0.0"
        },
        "tagline": "You Know, for Search"
    }
}</code></pre><h2>Indexation des documents</h2><p>Une fois connectés, nous pouvons indexer les documents à l&#x27;aide de mappings tels que <a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">semantic_text</a> pour la recherche sémantique et text pour les requêtes en texte intégral. Avec ces deux types de champs, nous pouvons également effectuer une <a href="https://www.elastic.co/what-is/hybrid-search">recherche hybride</a>.</p><p>Nous allons créer un nouveau fichier <code>load.js</code> pour générer les correspondances et télécharger les documents.</p><h3>Client Elasticsearch</h3><p>Nous devons d&#x27;abord instancier et authentifier le client :</p><pre><code>const { Client } = require("@elastic/elasticsearch");

const ELASTICSEARCH_ENDPOINT = "cluster/project_endpoint";
const ELASTICSEARCH_API_KEY = "apiKey";

const esClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { apiKey: ELASTICSEARCH_API_KEY },
});</code></pre><h3>Correspondances sémantiques</h3><p>Nous allons créer un index contenant des données sur un hôpital vétérinaire. Nous stockons les informations concernant le propriétaire, l&#x27;animal et les détails de la visite.</p><p>Les données sur lesquelles nous voulons effectuer une recherche en texte intégral, telles que les noms et les descriptions, seront stockées sous forme de texte. Les données des catégories, telles que l&#x27;espèce ou la race de l&#x27;animal, seront stockées sous forme de mots-clés.</p><p>En outre, nous copierons les valeurs de tous les champs dans un champ semantic_text afin de pouvoir effectuer une recherche sémantique sur ces informations également.</p><pre><code>const INDEX_NAME = "vet-visits";

const createMappings = async (indexName, mapping) => {
  try {
    const body = await esClient.indices.create({
      index: indexName,
      body: {
        mappings: mapping,
      },
    });

    console.log("Index created successfully:", body);
  } catch (error) {
    console.error("Error creating mapping:", error);
  }
};

await createMappings(INDEX_NAME, {
  properties: {
    owner_name: {
      type: "text",
      copy_to: "semantic_field",
    },
    pet_name: {
      type: "text",
      copy_to: "semantic_field",
    },
    species: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    breed: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    vaccination_history: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    visit_details: {
      type: "text",
      copy_to: "semantic_field",
    },
    semantic_field: {
      type: "semantic_text",
    },
  },
});</code></pre><h3>Aide en vrac</h3><p>Un autre avantage du client est qu&#x27;il est possible d&#x27;utiliser l&#x27;<a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-helpers.html#bulk-helper">assistant de masse</a> pour indexer par lots. L&#x27;assistant de masse nous permet de gérer facilement des choses comme la concurrence, les tentatives, et ce qu&#x27;il faut faire avec chaque document qui passe par la fonction et qui réussit ou échoue.</p><p>L&#x27;une des caractéristiques intéressantes de cette aide est qu&#x27;elle permet de travailler avec des flux. Cette fonction vous permet d&#x27;envoyer un fichier ligne par ligne au lieu de stocker le fichier complet dans la mémoire et de l&#x27;envoyer à Elasticsearch en une seule fois.</p><p>Pour télécharger les données vers Elasticsearch, créez un fichier appelé data.ndjson à la racine du projet et ajoutez les informations ci-dessous (vous pouvez également télécharger le fichier avec le jeu de données à partir d&#x27;<a href="https://github.com/Delacrobix/JS-client-best-practices_article/blob/main/data.ndjson">ici</a>) :</p><pre><code>{"owner_name":"Alice Johnson","pet_name":"Buddy","species":"Dog","breed":"Golden Retriever","vaccination_history":["Rabies","Parvovirus","Distemper"],"visit_details":"Annual check-up and nail trimming. Healthy and active."}
{"owner_name":"Marco Rivera","pet_name":"Milo","species":"Cat","breed":"Siamese","vaccination_history":["Rabies","Feline Leukemia"],"visit_details":"Slight eye irritation, prescribed eye drops."}
{"owner_name":"Sandra Lee","pet_name":"Pickles","species":"Guinea Pig","breed":"Mixed","vaccination_history":[],"visit_details":"Loss of appetite, recommended dietary changes."}
{"owner_name":"Jake Thompson","pet_name":"Luna","species":"Dog","breed":"Labrador Mix","vaccination_history":["Rabies","Bordetella"],"visit_details":"Mild ear infection, cleaning and antibiotics given."}
{"owner_name":"Emily Chen","pet_name":"Ziggy","species":"Cat","breed":"Mixed","vaccination_history":["Rabies","Feline Calicivirus"],"visit_details":"Vaccination update and routine physical."}
{"owner_name":"Tomás Herrera","pet_name":"Rex","species":"Dog","breed":"German Shepherd","vaccination_history":["Rabies","Parvovirus","Leptospirosis"],"visit_details":"Follow-up for previous leg strain, improving well."}
{"owner_name":"Nina Park","pet_name":"Coco","species":"Ferret","breed":"Mixed","vaccination_history":["Rabies"],"visit_details":"Slight weight loss; advised new diet."}
{"owner_name":"Leo Martínez","pet_name":"Simba","species":"Cat","breed":"Maine Coon","vaccination_history":["Rabies","Feline Panleukopenia"],"visit_details":"Dental cleaning. Minor tartar buildup removed."}
{"owner_name":"Rachel Green","pet_name":"Rocky","species":"Dog","breed":"Bulldog Mix","vaccination_history":["Rabies","Parvovirus"],"visit_details":"Skin rash, antihistamines prescribed."}
{"owner_name":"Daniel Kim","pet_name":"Mochi","species":"Rabbit","breed":"Mixed","vaccination_history":[],"visit_details":"Nail trimming and general health check. No issues."}</code></pre><p>Nous utilisons split2 pour streamer les lignes de fichiers pendant que l&#x27;assistant bulk les envoie à Elasticsearch.</p><pre><code>const { createReadStream } = require("fs");
const split = require("split2");
 
const indexData = async (filePath, indexName) => {
  try {
    console.log(`Indexing data from ${filePath} into ${indexName}...`);

    const result = await esClient.helpers.bulk({
      datasource: createReadStream(filePath).pipe(split()),

      onDocument: () => {
        return {
          index: { _index: indexName },
        };
      },
      onDrop(doc) {
        console.error("Error processing document:", doc);
      },
    });

    console.log("Bulk indexing successful elements:", result.items.length);
  } catch (error) {
    console.error("Error indexing data:", error);
    throw error;
  }
};

await indexData("./data.ndjson", INDEX_NAME);</code></pre><p>Le code ci-dessus lit un fichier .ndjson ligne par ligne et indexe en bloc chaque objet JSON dans un index Elasticsearch spécifié à l&#x27;aide de la méthode <code>helpers.bulk</code>. Il diffuse le fichier en utilisant <code>createReadStream</code> et <code>split2</code>, met en place des métadonnées d&#x27;indexation pour chaque document et enregistre tous les documents qui ne sont pas traités. Une fois l&#x27;opération terminée, il enregistre le nombre d&#x27;éléments indexés avec succès.</p><p>En lieu et place de la fonction <code>indexData</code>, vous pouvez télécharger le fichier directement via l&#x27;interface utilisateur à l&#x27;aide de Kibana et utiliser l&#x27;<a href="https://www.elastic.co/docs/manage-data/ingest/upload-data-files">interface utilisateur de téléchargement des fichiers de données.</a></p><p>Nous exécutons le fichier pour télécharger les documents vers notre cluster Elasticsearch.</p><p><code>node load.js</code></p><pre><code>Creating mappings for index vet-visits...
Index created successfully: { acknowledged: true, shards_acknowledged: true, index: 'vet-visits' }
Indexing data from ./data.ndjson into vet-visits...
Bulk indexing completed. Total documents: 10, Failed: 0</code></pre><h2>Recherche de données dans Elasticsearch</h2><p>En revenant à notre fichier <code>server.js</code>, nous allons créer différents points de terminaison pour effectuer une recherche lexicale, sémantique ou hybride.</p><p>En résumé, ces types de recherche ne s&#x27;excluent pas mutuellement, mais dépendent du type de question à laquelle vous devez répondre.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Type de requête</td><td style="border: 1px solid #ccc; padding: 4px;">Cas d'utilisation</td><td style="border: 1px solid #ccc; padding: 4px;">Exemple de question</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Requête lexicale</td><td style="border: 1px solid #ccc; padding: 4px;">Les mots ou racines de mots de la question sont susceptibles d'apparaître dans les documents de l'index. Similitude des jetons entre la question et les documents.</td><td style="border: 1px solid #ccc; padding: 4px;">Je cherche un t-shirt de sport bleu.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Requête sémantique</td><td style="border: 1px solid #ccc; padding: 4px;">Les mots de la question ne sont pas susceptibles de figurer dans les documents. Similitude conceptuelle entre la question et les documents.</td><td style="border: 1px solid #ccc; padding: 4px;">Je cherche des vêtements pour le froid.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Recherche hybride</td><td style="border: 1px solid #ccc; padding: 4px;">La question contient des éléments lexicaux et/ou sémantiques. Similitude toxique et sémantique entre les questions et les documents.</td><td style="border: 1px solid #ccc; padding: 4px;">Je cherche une robe taille S pour un mariage sur la plage.</td>
                    </tr>
              </tbody>
            </table>
          <p>Les parties <strong><em>lexicales </em></strong>de la question sont susceptibles de faire partie de titres et de descriptions, ou de noms de catégories, tandis que les parties <strong><em>sémantiques </em></strong>sont des concepts liés à ces domaines. Le <strong><em>bleu</em></strong> sera probablement un nom de catégorie ou une partie de la description, et le <strong><em>mariage à la plage</em></strong> ne le sera probablement pas, mais il peut être sémantiquement lié aux vêtements en lin.</p><h3>Requête lexicale (/search/lexic?q=&lt;query_term&gt;)</h3><p>La recherche lexicale, également appelée recherche en texte intégral, consiste à effectuer une recherche basée sur la similarité des mots-clés, c&#x27;est-à-dire qu&#x27;après une analyse, les documents qui contiennent les mots-clés de la recherche seront renvoyés.</p><p>Vous pouvez consulter notre tutoriel pratique sur la recherche lexicale <a href="https://www.elastic.co/demo-gallery/lexical-search">ici.</a></p><pre><code>app.get("/search/lexic", async (req, res) => {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

  try {
    const result = await esClient.search({
      index: INDEX_NAME,
      size: 5,
      body: {
        query: {
          multi_match: {
            query: q,
            fields: ["owner_name", "pet_name", "visit_details"],
          },
        },
      },
    });

    res.status(200).json({
      success: true,
      results: result.hits.hits
    });
  } catch (error) {
    console.error("Error performing search:", error);

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p>Nous testons avec : <strong><em>coupe-ongles</em></strong></p><pre><code>curl http://localhost:3000/search/lexic?q=nail%20trimming</code></pre><p>Réponse :</p><pre><code>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 2.7075968,
            "_source": {
                "pet_name": "Mochi",
                "owner_name": "Daniel Kim",
                "species": "Rabbit",
                "visit_details": "Nail trimming and general health check. No issues.",
                "breed": "Mixed",
                "vaccination_history": []
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 2.560356,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        }
    ]
}</code></pre><h3>Requête sémantique (/search/semantic?q=&lt;query_term&gt;)</h3><p>La recherche sémantique, contrairement à la recherche lexicale, permet de trouver des résultats similaires à la signification des termes de recherche par le biais d&#x27;une recherche vectorielle.</p><p>Vous pouvez consulter notre tutoriel pratique sur la recherche sémantique <a href="https://www.elastic.co/demo-gallery/semantic-search">ici.</a></p><pre><code>app.get("/search/semantic", async (req, res) => {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

  try {
    const result = await esClient.search({
      index: INDEX_NAME,
      size: 5,
      body: {
        query: {
          semantic: {
            field: "semantic_field",
            query: q
          },
        },
      },
    });

    res.status(200).json({
      success: true,
      results: result.hits.hits,
    });
  } catch (error) {
    console.error("Error performing search:", error);

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p>Nous testons avec : <strong><em>Qui s&#x27;est fait faire une pédicure ?</em></strong></p><pre><code>curl http://localhost:3000/search/semantic?q=Who%20got%20a%20pedicure?</code></pre><p>Réponse :</p><pre><code>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 4.861466,
            "_source": {
                "owner_name": "Daniel Kim",
                "pet_name": "Mochi",
                "species": "Rabbit",
                "breed": "Mixed",
                "vaccination_history": [],
                "visit_details": "Nail trimming and general health check. No issues."
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 4.7152824,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9RY6RJYBLe2GoFQ6-9n9",
            "_score": 1.6717153,
            "_source": {
                "pet_name": "Rex",
                "owner_name": "Tomás Herrera",
                "species": "Dog",
                "visit_details": "Follow-up for previous leg strain, improving well.",
                "breed": "German Shepherd",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Leptospirosis"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9xY6RJYBLe2GoFQ6-9n9",
            "_score": 1.5600781,
            "_source": {
                "pet_name": "Simba",
                "owner_name": "Leo Martínez",
                "species": "Cat",
                "visit_details": "Dental cleaning. Minor tartar buildup removed.",
                "breed": "Maine Coon",
                "vaccination_history": [
                    "Rabies",
                    "Feline Panleukopenia"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "-BY6RJYBLe2GoFQ6-9n9",
            "_score": 1.2696637,
            "_source": {
                "pet_name": "Rocky",
                "owner_name": "Rachel Green",
                "species": "Dog",
                "visit_details": "Skin rash, antihistamines prescribed.",
                "breed": "Bulldog Mix",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus"
                ]
            }
        }
    ]
}</code></pre><h3>Requête hybride (/search/hybrid?q=&lt;query_term&gt;)</h3><p>La recherche hybride nous permet de combiner la recherche sémantique et la recherche lexicale, et d&#x27;obtenir ainsi le meilleur des deux mondes : vous bénéficiez de la précision de la recherche par jeton, ainsi que de la proximité de sens de la recherche sémantique.</p><pre><code>app.get("/search/hybrid", async (req, res) => {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

  try {
    const result = await esClient.search({
      index: INDEX_NAME,
      body: {
        retriever: {
          rrf: {
            retrievers: [
              {
                standard: {
                  query: {
                    bool: {
                      must: {
                         multi_match: {
             query: q,
            fields: ["owner_name", "pet_name", "visit_details"],
          },
                      },
                    },
                  },
                },
              },
              {
                standard: {
                  query: {
                    bool: {
                      must: {
                        semantic: {
                          field: "semantic_field",
                          query: q,
                        },
                      },
                    },
                  },
                },
              },
            ],
          },
        },
        size: 5,
      },
    });

    res.status(200).json({
      success: true,
      results: result.hits.hits,
    });
  } catch (error) {
    console.error("Error performing search:", error);

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p>Nous testons avec &quot;<strong><em>Qui a reçu une pédicure ou un traitement dentaire ?&quot;</em></strong></p><pre><code>curl http://localhost:3000/search/hybrid?q=who%20got%20a%20pedicure%20or%20dental%20treatment</code></pre><p>Réponse :</p><pre><code>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "9xY6RJYBLe2GoFQ6-9n9",
            "_score": 0.032522473,
            "_source": {
                "pet_name": "Simba",
                "owner_name": "Leo Martínez",
                "species": "Cat",
                "visit_details": "Dental cleaning. Minor tartar buildup removed.",
                "breed": "Maine Coon",
                "vaccination_history": [
                    "Rabies",
                    "Feline Panleukopenia"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 0.016393442,
            "_source": {
                "pet_name": "Mochi",
                "owner_name": "Daniel Kim",
                "species": "Rabbit",
                "visit_details": "Nail trimming and general health check. No issues.",
                "breed": "Mixed",
                "vaccination_history": []
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015873017,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9RY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015625,
            "_source": {
                "pet_name": "Rex",
                "owner_name": "Tomás Herrera",
                "species": "Dog",
                "visit_details": "Follow-up for previous leg strain, improving well.",
                "breed": "German Shepherd",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Leptospirosis"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8xY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015384615,
            "_source": {
                "pet_name": "Luna",
                "owner_name": "Jake Thompson",
                "species": "Dog",
                "visit_details": "Mild ear infection, cleaning and antibiotics given.",
                "breed": "Labrador Mix",
                "vaccination_history": [
                    "Rabies",
                    "Bordetella"
                ]
            }
        }
    ]
}</code></pre><h2>Conclusion</h2><p>Dans cette première partie de notre série, nous avons expliqué comment configurer notre environnement et créer un serveur avec différents points de terminaison de recherche pour interroger les documents Elasticsearch en suivant les meilleures pratiques client/serveur. Consultez la <a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i">deuxième partie</a> de notre série, dans laquelle vous découvrirez les meilleures pratiques de production et comment exécuter le client Elasticsearch Node.js dans des environnements sans serveur.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment configurer le nombre de réplicas dans un index Elasticsearch]]></title>
            <description><![CDATA[Apprenez à configurer le number_of_replicas dans un index Elasticsearch afin d'améliorer les performances de rechercher et de renforcer la résilience en cas de panne de node. 
]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-index-number-of_replicas</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-index-number-of_replicas</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 14 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/23b96aaa1a38b1f4747b4a87695d816f24c0cf70-720x421.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch est conçu pour être un système distribué capable de gérer un grand nombre de données et d&#x27;assurer une haute disponibilité. L&#x27;une des fonctions clés qui permet cela est le concept de réplication d&#x27;index, qui est contrôlé par le paramètre <code>number_of_replicas</code>. Cet article aborde les détails de ce paramètre, ses implications et la manière de le configurer correctement.</p><h2>Le rôle des répliques dans Elasticsearch</h2><p>Dans Elasticsearch, un index est une collection de documents qui sont répartis sur plusieurs shards primaires. Chaque groupe primaire est un index Apache Lucene autonome, et les documents d&#x27;un index sont répartis entre tous les groupes primaires. Pour garantir la haute disponibilité et la redondance des données, Elasticsearch permet à chaque nuage d&#x27;avoir une ou plusieurs copies, appelées répliques.<br/><br/>Le paramètre <code>number_of_replicas</code> contrôle le nombre de répliques (copies) qu&#x27;Elasticsearch crée pour chaque réplique primaire d&#x27;un index. Par défaut, Elasticsearch crée une réplique pour chaque shard primaire, mais cela peut être modifié en fonction des besoins de votre système.</p><h2>Configuration du nombre de répliques (number_of_replicas)</h2><p>Le paramètre <code>number_of_replicas</code> peut être configuré au moment de la création de l&#x27;index ou mis à jour ultérieurement. Voici comment vous pouvez le définir lors de la création de l&#x27;index :</p><pre><code>PUT /my_index
{
  "settings": {
    "number_of_replicas": 2
  }
}</code></pre><p>Dans cet exemple, Elasticsearch créera deux réplicas pour chaque fichier primaire de l&#x27;index <code>my_index</code>.</p><p>Pour mettre à jour le paramètre <code>number_of_replicas</code> d&#x27;un index existant, vous pouvez utiliser l&#x27;API <code>_settings</code>:</p><pre><code>PUT /my_index/_settings
{
  "number_of_replicas": 3
}</code></pre><p>Cette commande mettra à jour l&#x27;index <code>my_index</code> pour qu&#x27;il y ait trois réplicas pour chaque groupe primaire.</p><h2>Implications du paramètre number_of_replicas (nombre de répliques)</h2><p>Le paramètre <code>number_of_replicas</code> a un impact significatif sur les performances et la résilience de votre <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-cluster/">cluster</a> Elasticsearch. Voici quelques points clés à prendre en considération :</p><ol><li><strong>Redondance et disponibilité des données :</strong> L&#x27;augmentation du site <code>number_of_replicas</code> améliore la disponibilité de vos données en créant davantage de copies de chaque groupe de données. Si un nœud tombe en panne, Elasticsearch peut toujours servir des données à partir des répliques sur les <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-node/">nœuds</a> restants.</li><li><strong>Performances de recherche :</strong> Les répliques peuvent répondre à des demandes de lecture. Le fait de disposer d&#x27;un plus grand nombre de répliques peut donc améliorer les performances en matière de recherche en répartissant la charge sur un plus grand nombre de répliques.</li></ol><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><ol><li><strong>Exigences en matière de stockage :</strong> Plus il y a de répliques, plus il y a d&#x27;espace de stockage. Vous devez vous assurer que votre cluster dispose d&#x27;une capacité suffisante pour stocker les répliques supplémentaires.</li><li><strong>Résilience en cas de défaillance d&#x27;un nœud :</strong> Le site <code>number_of_replicas</code> doit être défini en fonction du nombre de nœuds de votre cluster. Si le site <code>number_of_replicas</code> est égal ou supérieur au nombre de nœuds, votre cluster peut tolérer la défaillance de plusieurs nœuds sans perte de données.</li></ol><h2>Bonnes pratiques pour définir le nombre de répliques (number_of_replicas)</h2><p>Le réglage optimal de <code>number_of_replicas</code> dépend des exigences spécifiques de votre système. Toutefois, voici quelques bonnes pratiques générales :</p><ul><li>Pour un cluster à un seul nœud, <code>number_of_replicas</code> doit être fixé à 0, car il n&#x27;y a pas d&#x27;autres nœuds pour contenir des répliques.</li><li>Pour un cluster à plusieurs nœuds, <code>number_of_replicas</code> doit être réglé sur au moins 1 pour assurer la redondance des données et la haute disponibilité.</li><li>Si la performance de la recherche est une priorité, envisagez d&#x27;augmenter le site <code>number_of_replicas</code>. Toutefois, il convient de garder à l&#x27;esprit le compromis entre les performances d&#x27;écriture et les exigences en matière de stockage.</li><li>Assurez-vous toujours que votre cluster dispose d&#x27;une capacité suffisante pour stocker les répliques supplémentaires.</li></ul>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/mapping-embeddings-to-elasticsearch-field-types</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/mapping-embeddings-to-elasticsearch-field-types</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f98fdff837db55b466780c0bae672aa6f6c3a966-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>L&#x27;utilisation d&#x27;enchâssements pour améliorer la pertinence et la précision de la recherche d&#x27;informations s&#x27;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&#x27;entre eux et comment ils sont liés à la génération d&#x27;encarts et aux stratégies d&#x27;utilisation, à la fois lors de l&#x27;indexation et de l&#x27;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&#x27;audio, où presque toutes les dimensions sont pertinentes... Ces vecteurs sont générés à l&#x27;aide de modèles d&#x27;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&#x27;autres documents.</p><p>Dans Elasticsearch, les vecteurs denses peuvent avoir jusqu&#x27;à 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&#x27;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&#x27;intégration lorsqu&#x27;un plus grand contrôle est nécessaire, comme l&#x27;utilisation de vecteurs pré-générés, l&#x27;application de fonctions de similarité personnalisées ou l&#x27;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&#x27;objectif est de comparer le sens global des textes, même s&#x27;ils ne partagent pas les mêmes termes.</p><p>Le champ de vecteurs denses est idéal lorsque vous disposez déjà d&#x27;un pipeline externe de génération d&#x27;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&#x27;intégration et une souplesse totale en matière de génération et d&#x27;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&#x27;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&#x27;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&#x27;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&#x27;application d&#x27;une requête k-NN à un champ vectoriel dense :</p><pre><code>{
  "knn": {
    "field": "my_dense_vector",
    "k": 10,
    "num_candidates": 50,
    "query_vector": [/* vector generated by model */]
  }
}</code></pre><p>Outre la requête k-NN, s&#x27;il est nécessaire de personnaliser la notation des documents, il est également possible d&#x27;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&#x27;une manière plus contrôlée. Voir l&#x27;exemple :</p><pre><code>{
"script_score": {
    "query": { "match_all": {} },
    "script": {
      "source": "cosineSimilarity(params.query_vector,
'my_dense_vector') + 1.0",
      "params": {
        "query_vector": [/* vector */]
      }
    }
  }
}</code></pre><p>Si vous souhaitez aller plus loin, je vous recommande d&#x27;explorer l&#x27;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&#x27;une recherche plus précise en termes lexicaux, sans sacrifier l&#x27;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&#x27;influence de mots spécifiques dans la requête, les types de vecteurs épars vous permettent d&#x27;ajuster manuellement le poids des termes afin d&#x27;optimiser le classement des résultats.</p><p>Parmi les principaux avantages, citons la transparence de la recherche, puisqu&#x27;il est possible de comprendre clairement pourquoi un document a été jugé pertinent, et l&#x27;efficacité du stockage, puisque seuls les jetons dont la valeur n&#x27;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&#x27;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&#x27;un vecteur de requête au format jeton/valeur. Vous trouverez ci-dessous un exemple de requête :</p><pre><code>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "query_vector": {
        "token1": 0.6,
        "token2": 0.2,
        "token3": 0.9
      }
    }
  }
}</code></pre><p>Si vous préférez utiliser un modèle formé, il est possible d&#x27;utiliser un point final d&#x27;inférence qui transforme automatiquement le texte de la requête en un vecteur peu dense :</p><pre><code>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "inference_id": "the inference ID to produce the token/weights",
      "query": "search text"
    }
  }
}</code></pre><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&#x27;utiliser la recherche sémantique dans Elasticsearch. Il gère automatiquement la génération de l&#x27;intégration, à la fois au moment de l&#x27;indexation et de l&#x27;interrogation, par le biais d&#x27;un point final d&#x27;inférence. Cela signifie que vous n&#x27;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&#x27;effort technique et sans avoir à manipuler les vecteurs manuellement. Ce champ automatise des étapes telles que la génération de l&#x27;encastrement et le mappage de la recherche vectorielle, ce qui rend l&#x27;installation plus rapide et plus pratique.</p><p>Vous devriez envisager d&#x27;utiliser <code>semantic_text</code> si vous appréciez la <strong>simplicité et l&#x27;abstraction</strong>, car il <strong>élimine la complexité de la configuration manuelle des mappings, de la génération d&#x27;embedding et des pipelines d&#x27;ingestion</strong>. Il suffit de sélectionner le modèle d&#x27;inférence et Elasticsearch s&#x27;occupe du reste.</p><p>Parmi les principaux avantages, citons la <strong>génération automatique de l&#x27;intégration,</strong> effectuée à la fois pendant l&#x27;indexation et l&#x27;interrogation, et le <strong>mappage prêt à l&#x27;emploi</strong>, qui est préconfiguré pour prendre en charge le modèle d&#x27;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&#x27;en trouve grandement améliorée, en particulier pour les équipes qui souhaitent fournir rapidement de la valeur ajoutée sans avoir à s&#x27;occuper de l&#x27;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&#x27;utiliser des modèles standard du marché, pour autant qu&#x27;ils soient disponibles en tant que points de terminaison d&#x27;inférence dans Elasticsearch. Cependant, <strong>il ne prend pas en charge les encastrements générés de l&#x27;extérieur</strong>, comme c&#x27;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&#x27;utiliser une requête différente selon le type d&#x27;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&#x27;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&#x27;inférence pour intégrer la requête, mais si aucun n&#x27;est spécifié, le même point final que celui utilisé lors de l&#x27;indexation sera appliqué à la requête.</p><pre><code>{
  "query": {
    "semantic": {
      "field": "semantic_text_field",
      "query": "search text"
    }
  }
}</code></pre><p>Pour en savoir plus, je vous propose de lire l&#x27;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&#x27;utilisation initiaux. Lorsqu&#x27;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&#x27;utilisation, de l&#x27;infrastructure disponible et de la maturité de votre pile d&#x27;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><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">Type de champ texte sémantique</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/sparse-vector.html">Type de champ vectoriel épars</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">Type de champ vectoriel dense</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">Requête sémantique</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html">Requête sur les vecteurs épars</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">Recherche kNN</a></li><li><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Nouveau mappage semantic_text d&#x27;Elasticsearch : Simplifier la recherche sémantique</a></li><li><a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">Comprendre les encastrements de vecteurs épars à l&#x27;aide de modèles ML entraînés</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Exclusion des champs Elasticsearch de l'indexation]]></title>
            <description><![CDATA[Apprenez comment configurer Elasticsearch pour exclure des champs, les principales raisons d'exclure des champs de l'indexation, et les bonnes pratiques à suivre.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/excluding-elasticsearch-fields-from-indexing</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/excluding-elasticsearch-fields-from-indexing</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Mon, 12 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3aa0b481994d2445ba979d3c79fff64c5ee6676a-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans Elasticsearch, l&#x27;indexation fait référence au processus de stockage et d&#x27;organisation des données de manière à les rendre facilement consultables. Si l&#x27;indexation de tous les champs d&#x27;un document peut s&#x27;avérer utile dans certains cas, il peut arriver que vous souhaitiez exclure certains champs de l&#x27;indexation. Cela permet d&#x27;améliorer les performances, de réduire les coûts de stockage et de minimiser la taille globale de votre index Elasticsearch.</p><p>Dans cet article, nous examinerons les raisons d&#x27;exclure des champs de l&#x27;indexation, comment configurer Elasticsearch pour exclure des champs spécifiques et quelques bonnes pratiques à suivre pour ce faire.</p><h2>Raisons d&#x27;exclure des champs de l&#x27;indexation</h2><ol><li><strong>Performance : </strong>L&#x27;indexation de tous les champs d&#x27;un document peut augmenter le temps d&#x27;indexation et ralentir les performances de recherche. En excluant les champs qui ne sont pas nécessaires à la recherche ou à l&#x27;agrégation, vous pouvez améliorer les performances globales de votre cluster Elasticsearch.</li><li><strong>Stockage : </strong>L&#x27;indexation des champs consomme de l&#x27;espace de stockage. L&#x27;exclusion des champs qui ne sont pas nécessaires à la recherche ou à l&#x27;agrégation peut contribuer à réduire les besoins en stockage de votre cluster Elasticsearch.</li><li><strong>Taille de l&#x27;index : </strong>La taille d&#x27;un index Elasticsearch est directement liée au nombre de champs indexés. En excluant les champs inutiles, vous pouvez réduire la taille de votre index, ce qui permet d&#x27;accélérer les performances de recherche et d&#x27;indexation.</li></ol><h2>Configurer Elasticsearch pour exclure des champs</h2><p>Pour exclure un champ de l&#x27;indexation dans Elasticsearch, vous pouvez utiliser la propriété &quot;index&quot; dans le mappage du champ. En définissant la propriété &quot;index&quot; à &quot;false&quot;, Elasticsearch n&#x27;indexera pas le champ, et il ne sera pas consultable ni disponible pour les agrégations.</p><p>Voici un exemple d&#x27;exclusion d&#x27;un champ de l&#x27;indexation à l&#x27;aide du mappage Elasticsearch :</p><pre><code>PUT /my_index
{
  "mappings": {
    "properties": {
      "field_to_exclude": {
        "type": "text",
        "index": false
      }
    }
  }
}</code></pre><p>Dans cet exemple, nous créons un nouvel index appelé &quot;my_index&quot; avec un seul champ appelé &quot;field_to_exclude&quot;. En définissant la propriété &quot;index&quot; à &quot;false&quot;, nous indiquons à Elasticsearch de ne pas indexer ce champ. Le champ reste cependant disponible dans le document source.</p><h2>Meilleures pratiques pour exclure des champs de l&#x27;indexation</h2><ol><li><strong>Analysez vos données : </strong>Avant d&#x27;exclure des champs de l&#x27;indexation, il est essentiel d&#x27;analyser vos données et de comprendre quels champs sont nécessaires à la recherche et à l&#x27;agrégation. Cela vous aidera à prendre des décisions éclairées sur les champs à exclure.</li><li><strong>Testez vos modifications : </strong>Lorsque vous excluez des champs de l&#x27;indexation, il est essentiel de tester vos modifications pour vous assurer que vos fonctionnalités de recherche et d&#x27;agrégation fonctionnent toujours comme prévu. Cela peut vous aider à éviter des problèmes inattendus ou des problèmes de performance.</li><li><strong>Contrôlez les performances :</strong> Après avoir exclu des champs de l&#x27;indexation, surveillez les performances de votre cluster Elasticsearch pour vous assurer que vos modifications ont eu l&#x27;effet escompté. Cela peut vous aider à identifier les optimisations supplémentaires qui pourraient être nécessaires.</li><li><strong>Utiliser le filtrage à la source :</strong> Si vous avez besoin de stocker un champ dans Elasticsearch mais que vous ne souhaitez pas qu&#x27;il soit consultable ou disponible pour les agrégations, envisagez d&#x27;utiliser le filtrage des sources. Cela vous permet de stocker le champ dans le champ _source mais de l&#x27;exclure de l&#x27;index.</li></ol><h2>Conclusion</h2><p>L&#x27;exclusion de champs de l&#x27;indexation dans Elasticsearch peut contribuer à améliorer les performances, à réduire les coûts de stockage et à minimiser la taille globale de votre index. En analysant soigneusement vos données et en comprenant quels champs sont nécessaires à la recherche et à l&#x27;agrégation, vous pouvez prendre des décisions éclairées sur les champs à exclure. Testez toujours vos modifications et surveillez les performances de votre cluster Elasticsearch pour vous assurer que vos optimisations ont l&#x27;effet escompté.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Suppression d'un champ d'un document dans Elasticsearch]]></title>
            <description><![CDATA[Apprenez à supprimer des champs de documents Elasticsearch à l'aide de l'API de mise à jour, de scripts ou de la réindexation pour des suppressions uniques ou en masse.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-delete-field-from-document</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-delete-field-from-document</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 09 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/89278eb7309b7f3018c61be2b514d1fd25b9564d-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans Elasticsearch, il est fréquent de devoir supprimer un champ d&#x27;un document. Cela peut s&#x27;avérer utile lorsque vous souhaitez supprimer des informations inutiles ou obsolètes de votre index. Dans cet article, nous aborderons différentes méthodes pour supprimer un champ d&#x27;un document dans Elasticsearch, avec des exemples et des instructions pas à pas. </p><h2>Méthode 1 : Utilisation de l&#x27;API de mise à jour</h2><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/update-document">API Update</a> vous permet de mettre à jour un document en fournissant un script qui modifie la source du document. Vous pouvez utiliser cette API pour supprimer un champ d&#x27;un document en lui attribuant la valeur null. Voici un guide étape par étape pour y parvenir :</p><p>1. Identifiez l&#x27;index, le type de document (si vous utilisez Elasticsearch 6.x ou une version antérieure) et l&#x27;ID du document que vous souhaitez mettre à jour.</p><p>2. Utilisez l&#x27;API de mise à jour avec un script qui définit le champ comme nul ou, mieux encore, le supprime du document source. L&#x27;exemple suivant montre comment supprimer le champ &quot;field_to_delete&quot; d&#x27;un document dont l&#x27;ID est &quot;1&quot; dans l&#x27;index &quot;my_index&quot; :</p><pre><code>POST /my_index/_update/1
{
  "script": "ctx._source.remove('field_to_delete')"
}</code></pre><p>3. Exécuter la demande. En cas de succès, Elasticsearch renvoie une réponse indiquant que le document a été mis à jour.</p><p>Note : Cette méthode ne supprime le champ que du document spécifié. Le champ existera toujours dans la cartographie et dans les autres documents de l&#x27;index.</p><h2>Méthode 2 : Réindexation avec une source modifiée</h2><p>Si vous souhaitez supprimer un champ de tous les documents d’un index, vous pouvez utiliser l’<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-reindex">API Reindex</a> pour créer un nouvel index avec la source modifiée. Voici comment procéder :</p><p>1. Créez un nouvel index avec les mêmes paramètres et mappages que l&#x27;index original. Vous pouvez utiliser l&#x27;API Get Index pour récupérer les paramètres et les mappages de l&#x27;index original.</p><p>2. Utilisez l&#x27;API Reindex pour copier les documents de l&#x27;index original vers le nouvel index, tout en supprimant le champ de la source. L&#x27;exemple suivant montre comment supprimer le champ &quot;field_to_delete&quot; de tous les documents de l&#x27;index &quot;my_index&quot; :</p><pre><code>POST /_reindex
{
  "source": {
    "index": "my_index"
  },
  "dest": {
    "index": "new_index"
  },
  "script": {
    "source": "ctx._source.remove('field_to_delete')"
  }
}</code></pre><p><br/>3. Vérifier que le nouvel index contient les documents corrects avec le champ supprimé.</p><p>4. Si tout semble correct, vous pouvez supprimer l&#x27;index original et, si nécessaire, ajouter un alias au nouvel index portant le nom de l&#x27;index original.</p><h2>Méthode 3 : Mise à jour du mapping et réindexation</h2><p>Si vous souhaitez supprimer un champ du mappage et de tous les documents d&#x27;un index, vous pouvez mettre à jour le mappage, puis réindexer les documents. Voici comment procéder :</p><p>1. Créez un nouvel index avec les mêmes paramètres que l&#x27;index original.</p><p>2. Récupérer les mappings de l&#x27;index original à l&#x27;aide de l&#x27;API &quot;Get Mapping&quot;.</p><p>3. Modifiez les correspondances en supprimant le champ que vous souhaitez supprimer.</p><p>4. Appliquez les mappages modifiés au nouvel index à l&#x27;aide de l&#x27;API Put Mapping.</p><p>5. Utilisez l&#x27;API de réindexation pour copier les documents de l&#x27;index d&#x27;origine vers le nouvel index, comme décrit dans la méthode 2.</p><p>6. Vérifiez que le nouvel index contient les documents corrects avec le champ supprimé et que le champ n&#x27;est pas présent dans le mappage.</p><p>7. Si tout semble en ordre, vous pouvez supprimer l’index original et, si nécessaire, ajouter un alias au nouvel index avec le nom de l’index original.</p><h2>Conclusion</h2><p>Dans cet article, nous avons abordé trois méthodes pour supprimer un champ d&#x27;un document dans Elasticsearch : l&#x27;utilisation de l&#x27;API de mise à jour, la réindexation avec une source modifiée et la mise à jour du mappage et la réindexation. Chaque méthode a ses propres cas d&#x27;utilisation et ses propres compromis, choisissez donc celle qui répond le mieux à vos besoins. N&#x27;oubliez jamais de tester vos modifications et de vérifier les résultats avant de les appliquer aux environnements de production.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment joindre deux index dans Elasticsearch]]></title>
            <description><![CDATA[Explication de l'utilisation des termes query, Logstash elasticsearch filter, the enrich processor et ES|QL pour joindre deux index dans Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-join-two-indexes</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-join-two-indexes</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Carly Richmond]]></dc:creator>
            <pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0d4736d10fa3e12e6233cd59993299c7bd48911b-680x450.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans Elasticsearch, joindre deux index n&#x27;est pas aussi simple que dans les bases de données relationnelles SQL traditionnelles. Cependant, il est possible d&#x27;obtenir des résultats similaires en utilisant certaines techniques et fonctionnalités fournies par Elasticsearch.</p><p>Historiquement, de nombreuses personnes ont utilisé le <a href="https://www.elastic.co/fr/docs/reference/elasticsearch/mapping-reference/nested"><code>nested</code> type de</a> champ comme un mécanisme permettant de relier différents indices entre eux. Cependant, elle a été limitée par des requêtes coûteuses et une prise en charge incomplète dans Kibana, en particulier pour les visualisations de Lens.</p><p>Cet article se penche sur le processus de jonction de deux index dans Elasticsearch, en se concentrant sur les approches suivantes : </p><ol><li>Utilisation de la requête <code>terms</code></li><li>Utilisation du processeur <code>enrich</code> dans les pipelines d&#x27;ingestion</li><li>Logstash <code>elasticsearch</code> filter plugin</li><li>ES|QL <code>ENRICH</code></li><li>ES|QL <code>LOOKUP JOIN</code></li></ol><h2>Utilisation des termes de la requête</h2><p>La <a href="https://www.elastic.co/fr/docs/reference/query-languages/query-dsl/query-dsl-terms-query">requête terms</a> est l&#x27;un des moyens les plus efficaces de joindre deux index dans Elasticsearch. Cette requête permet de retrouver des documents contenant un ou plusieurs termes exacts dans un champ spécifique. Nous verrons ici comment l&#x27;utiliser pour joindre deux indices.</p><p>Tout d&#x27;abord, vous devez extraire les données requises du premier index. Cela peut être fait en utilisant une simple requête GET et en extrayant les valeurs de l&#x27;attribut <code>_source</code>.</p><pre><code># Simple GET request
GET first_index/_search</code></pre><p>Une fois que vous avez les données du premier index, vous pouvez les utiliser pour interroger le deuxième index. Cette opération s&#x27;effectue à l&#x27;aide de la requête <code>terms</code>, dans laquelle vous indiquez le champ et les valeurs que vous souhaitez faire correspondre.</p><p>En voici un exemple :</p><pre><code>GET second_index/_search
{
  "query": {
    "terms": {
      "field_in_second_index": ["value1_from_first_index", "value2_from_first_index"]
    }
  }
}</code></pre><p><br/>Dans cet exemple, <code>field_in_second_index</code> est le champ du deuxième index que vous souhaitez faire correspondre aux valeurs du premier index. <code>value1_from_first_index</code> et <code>value2_from_first_index</code> sont les valeurs du premier index que vous voulez faire correspondre au deuxième index.</p><p>La recherche de termes permet également d&#x27;effectuer les deux étapes ci-dessus en une seule fois à l&#x27;aide d&#x27;une technique appelée &quot; <a href="https://www.elastic.co/fr/docs/reference/query-languages/query-dsl/query-dsl-terms-query#query-dsl-terms-lookup">recherche de termes&quot;.</a> Elasticsearch se chargera de récupérer de manière transparente les valeurs à faire correspondre à partir d&#x27;un autre index. Par exemple, si vous disposez d&#x27;un index des équipes contenant une liste de joueurs :</p><pre><code>PUT teams/_doc/team1
{
  "players":   ["john", "bill", "michael"]
}
PUT teams/_doc/team2
{
  "players":   ["aaron", "joe", "donald"]
}</code></pre><p>Il est possible d&#x27;interroger un index de personnes pour connaître toutes les personnes jouant dans l&#x27;équipe 1, comme indiqué ci-dessous :</p><pre><code>GET people/_search?pretty
{
  "query": {
    "terms": {
        "name" : {
            "index" : "teams",
            "id" : "team1",
            "path" : "players"
        }
    }
  }
}</code></pre><p>Dans l&#x27;exemple ci-dessus, Elasticsearch récupère de manière transparente les noms des joueurs à partir du document avec l&#x27;identifiant team1 dans l&#x27;index teams (c&#x27;est-à-dire &quot;john&quot;, &quot;bill&quot; et &quot;michael&quot;) et trouver tous les documents de l&#x27;index des personnes contenant l&#x27;une de ces valeurs dans le champ &quot;nom&quot;.</p><p>Pour les curieux, la requête SQL équivalente serait la suivante :</p><pre><code>SELECT p.* FROM people p
INNER JOIN teams t ON p.name = t.players</code></pre><h2>Utilisation du processeur d&#x27;enrichissement</h2><p>Le <a href="https://www.elastic.co/fr/docs/reference/enrich-processor/enrich-processor"><code>enrich</code> processeur</a> est un autre outil puissant qui peut être utilisé pour joindre deux index dans Elasticsearch. Ce processeur enrichit les données des documents entrants en ajoutant des données provenant d&#x27;un index d&#x27;enrichissement prédéfini.</p><p>Voici comment utiliser le processeur d&#x27;enrichissement pour joindre deux indices :</p><p>1. Tout d&#x27;abord, vous devez créer une politique d&#x27;enrichissement. Cette politique définit l&#x27;index à utiliser pour l&#x27;enrichissement, le champ à utiliser et le(s) champ(s) à utiliser pour enrichir les documents entrants.</p><p>En voici un exemple :</p><pre><code>PUT _enrich/policy/my_enrich_policy
{
  "match": {
    "indices": "first_index",
    "match_field": "field_in_first_index",
    "enrich_fields": ["field_to_enrich"]
  }
}</code></pre><p>2. Une fois la politique créée, vous devez l&#x27;exécuter pour créer l&#x27;index d&#x27;enrichissement à partir de votre nouvelle politique :</p><pre><code>PUT _enrich/policy/my_enrich_policy/_execute</code></pre><p>Cela permet de créer un nouvel index caché enrichi qui sera utilisé lors de l&#x27;enrichissement. Selon la taille de l&#x27;index source, cette opération peut prendre un certain temps. Assurez-vous que la politique d&#x27;enrichissement est entièrement construite avant de passer à l&#x27;étape suivante.</p><p>3. Une fois la politique d&#x27;enrichissement élaborée, vous pouvez utiliser le processeur d&#x27;enrichissement dans un pipeline d&#x27;acquisition pour enrichir les données des documents entrants :</p><pre><code>PUT _ingest/pipeline/my_pipeline
{
  "processors": [
    {
      "enrich": {
        "policy_name": "my_enrich_policy",
        "field": "field_in_second_index",
        "target_field": "enriched_field"
      }
    }
  ]
}</code></pre><p>Dans cet exemple, <code>field_in_second_index</code> est le champ du deuxième index qui doit correspondre au champ <code>match_field</code> du premier index. <code>enriched_field</code> est le nouveau champ du deuxième index qui contiendra les données enrichies de <code>enrich_fields</code> dans le premier index.</p><p>L&#x27;un des inconvénients de cette approche est que si les données changent sur <code>first_index</code>, la politique d&#x27;enrichissement doit être réexécutée. L&#x27;index enrichi n&#x27;est pas mis à jour ou synchronisé automatiquement à partir de l&#x27;index source à partir duquel il a été construit. Cependant, si <code>first_index</code> est relativement stable, cette approche fonctionne bien.</p><h2>Plugin de filtre Logstash elasticsearch</h2><p>Si vous utilisez Logstash, une autre option similaire au processeur <code>enrich</code> décrit ci-dessus consiste à utiliser le plugin de filtre <code>elasticsearch</code> pour ajouter des champs pertinents à l&#x27;événement sur la base d&#x27;une requête spécifiée. La configuration de notre pipeline Logstash se trouve dans un fichier <code>.conf</code>, tel que <code>my-pipeline.conf</code>.</p><p>Imaginons que notre pipeline extrait des logs d&#x27;Elasticsearch à l&#x27;aide du <a href="https://www.elastic.co/fr/docs/reference/logstash/plugins/plugins-inputs-elasticsearch"><code>elasticsearch</code> plugin</a> d&#x27;entrée, avec une requête pour réduire la sélection :</p><pre><code>input {
  # Read all documents from Elasticsearch matching the given query
  elasticsearch {
    hosts => "localhost"
    query => '{ "query": { "match": { "statuscode": 200 } }, "sort": [ "_doc" ] }'
  }
}</code></pre><p>Si nous voulons enrichir ces messages avec des informations provenant d&#x27;un index donné, nous pouvons utiliser le <a href="https://www.elastic.co/fr/docs/reference/logstash/plugins/plugins-filters-elasticsearch"><code>elasticsearch</code> plugin de filtre</a> dans la <code>filter</code> section pour enrichir nos journaux :</p><pre><code>filter {
   elasticsearch {
      hosts => ["localhost"]
      index => "index_name"
      query => "type:start AND operation:%{[opid]}"
      fields => { "@timestamp" => "started" }
   }
}</code></pre><p>Le code ci-dessus trouvera les documents de l&#x27;index <code>index_name</code> où <code>type</code> est le début et le champ opération correspond au champ spécifié <code>opid</code>, puis copiera la valeur du champ <code>@timestamp</code> dans un nouveau champ nommé <code>started</code>.</p><p>Les documents enrichis sont ensuite envoyés à la source de sortie appropriée, en l&#x27;occurrence Elasticsearch à l&#x27;aide du <a href="https://www.elastic.co/fr/docs/reference/logstash/plugins/plugins-outputs-elasticsearch"><code>elasticsearch</code> plugin de</a> sortie:</p><pre><code>output {
    elasticsearch {
        hosts => "localhost"
        data_stream => "true"
    }
}</code></pre><p>Si vous utilisez déjà Logstash, cette option peut être utile pour consolider votre logique d&#x27;enrichissement en un seul endroit et la traiter au fur et à mesure de l&#x27;arrivée de nouveaux événements. Cependant, si vous ne l&#x27;êtes pas, cela ajoute de la complexité à votre solution, et un autre composant que vous devez gérer et entretenir.</p><h2>ES|QL ENRICH</h2><p>L&#x27;introduction d&#x27;<a href="https://www.elastic.co/fr/docs/explore-analyze/query-filter/languages/esql">ES|QL</a>, qui a été généralisée dans la version 8.14, est un langage de requête en ligne pris en charge par Elasticsearch qui permet le filtrage, la transformation et l&#x27;analyse des données. La commande de traitement ENRICH permet d&#x27;ajouter des données à partir d&#x27;indices existants à l&#x27;aide d&#x27;une politique d&#x27;enrichissement.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03aadddc08afffff3f6526c9c052999c97fa09dd-1600x989.png" style="width: 100%;" /><p>En reprenant la même politique <code>my_enrich_policy</code> que dans l&#x27;exemple original du processeur d&#x27;enrichissement, l&#x27;exemple ES|QL ressemblerait à ce qui suit :</p><pre><code>FROM first_index
| WHERE field_in_first_index IS NOT NULL
| ENRICH my_enrich_policy</code></pre><p>Il est également possible de remplacer les champs de correspondance et d&#x27;enrichissement qui, dans notre exemple, sont respectivement <code>field_in_first_index</code> et <code>field_to_enrich</code>:</p><pre><code>FROM first_index
| WHERE field_in_first_index IS NOT NULL
| ENRICH my_enrich_policy ON another_field_in_first_index WITH different_field_to_enrich</code></pre><p>Bien que la limitation évidente soit que vous devez d&#x27;abord spécifier une politique d&#x27;enrichissement, ES|QL offre la flexibilité de modifier les champs selon les besoins.</p><h2>ES|QL LOOKUP JOIN</h2><p>Elasticsearch 8.18 introduit une nouvelle façon de joindre des index dans Elasticsearch, à savoir la commande <code>LOOKUP JOIN</code>. Cette commande fonctionne comme une jointure gauche-externe de type SQL en utilisant le nouveau <a href="https://www.elastic.co/fr/docs/reference/elasticsearch/index-settings/index-modules#index-mode-setting">mode d&#x27;indexation du</a> côté droit de la jointure.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1d73495979c4d6bb675c4c966ea86d9a72dc1c48-510x605.png" style="width: 100%;" /><p>Si l&#x27;on reprend l&#x27;exemple précédent, la nouvelle requête est la suivante, où <code>match_field</code> doit être présent à la fois dans <code>first_index</code> et <code>second_index</code>:</p><pre><code>FROM first_index
| WHERE field_in_first_index IS NOT NULL
| LOOKUP JOIN second_index ON match_field</code></pre><p>L&#x27;avantage de LOOKUP JOIN par rapport aux autres approches est qu&#x27;il ne nécessite pas de politique <code>enrich</code>, et donc le traitement supplémentaire associé à la mise en place de la politique. Elle est utile lorsque l&#x27;on travaille avec des données d&#x27;enrichissement qui changent fréquemment, contrairement aux autres approches abordées dans cet article.</p><h2>Conclusion</h2><p>En conclusion, bien qu&#x27;Elasticsearch ne prenne pas en charge les opérations de jointure traditionnelles, il offre diverses fonctionnalités qui peuvent être utilisées pour obtenir des résultats similaires. Plus précisément, nous avons vu comment réaliser des opérations de jointure à l&#x27;aide de :</p><ol><li>La requête <code>terms</code></li><li>Le processeur <code>enrich</code> dans les pipelines d&#x27;acquisition</li><li>Logstash <code>elasticsearch</code> filter plugin</li><li>ES|QL <code>ENRICH</code></li><li>ES|QL <code>LOOKUP JOIN</code></li></ol><p>Il est important de noter que ces méthodes ont leurs limites et doivent être utilisées judicieusement en fonction des besoins spécifiques et de la nature des données.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comprendre la notation Elasticsearch et l'API Explain]]></title>
            <description><![CDATA[Découvrez les mécanismes de notation d’Elasticsearch et la fonction pratique de notation pour réaliser un audit de la pertinence de la recherche et améliorer le classement des documents grâce à l’API Explain.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-scoring-and-explain-api</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-scoring-and-explain-api</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Mon, 05 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a70c5403064d5bbceff66a17373332362227f13c-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch est un moteur de recherche puissant qui fournit des résultats de recherche rapides et pertinents en calculant un score pour chaque document dans l&#x27;index. Ce score est un facteur crucial pour déterminer l&#x27;ordre des résultats de la recherche. Dans cet article, nous allons nous plonger dans le mécanisme de notation d&#x27;Elasticsearch et explorer l&#x27;API Explain, qui aide à comprendre le processus de notation.</p><h2>Mécanismes de notation dans Elasticsearch</h2><p>Elasticsearch utilise par défaut un modèle de notation appelé Practical Scoring Function (BM25). Ce modèle est basé sur la théorie probabiliste de la recherche d&#x27;informations et prend en compte des facteurs tels que la fréquence des termes, la fréquence inverse des documents et la normalisation de la longueur des champs. Examinons brièvement ces facteurs :</p><ol><li><strong>Fréquence des termes (TF) :</strong> Elle représente le nombre de fois qu&#x27;un terme apparaît dans un document. Une fréquence de terme plus élevée indique une relation plus forte entre le terme et le document.</li><li><strong>Fréquence inverse des documents (IDF) :</strong> Ce facteur mesure l&#x27;importance d&#x27;un terme dans l&#x27;ensemble de la collection de documents. Un terme qui apparaît dans de nombreux documents est considéré comme moins important, tandis qu&#x27;un terme qui apparaît dans moins de documents est considéré comme plus important.</li><li><strong>Normalisation de la longueur du champ</strong>: Ce facteur tient compte de la longueur du champ dans lequel le terme apparaît. Les champs plus courts ont plus de poids, car le terme est considéré comme plus significatif dans un champ plus court.</li></ol><h2>Utiliser l&#x27;API Expliciter</h2><p>L&#x27;API Explain d&#x27;Elasticsearch est un outil précieux pour comprendre le processus de notation. Il fournit une explication détaillée de la manière dont la note d&#x27;un document spécifique a été calculée. Pour utiliser l&#x27;API Expliciter, vous devez envoyer une requête GET au point de terminaison suivant :</p><pre><code>GET /<index>/_explain/<document_id></code></pre><p>Dans le corps de la demande, vous devez indiquer la requête pour laquelle vous voulez comprendre la notation. En voici un exemple :</p><pre><code>{
  "query": {
    "match": {
      "title": "elasticsearch"
    }
  }
}</code></pre><p>La réponse de l&#x27;API Expliciter comprendra une ventilation détaillée du processus de notation, y compris les facteurs individuels (TF, IDF et normalisation de la longueur de champ) et leurs contributions à la note finale. Voici un exemple de réponse :</p><pre><code>{
  "_index": "example_index",
  "_type": "_doc",
  "_id": "1",
  "matched": true,
  "explanation": {
    "value": 1.2,
    "description": "weight(title:elasticsearch in 0) [PerFieldSimilarity], result of:",
    "details": [
      {
        "value": 1.2,
        "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
        "details": [
          {
            "value": 2.2,
            "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
            "details": [
              {
                "value": 1,
                "description": "docFreq",
                "details": []
              },
              {
                "value": 1,
                "description": "docCount",
                "details": []
              }
            ]
          },
          {
            "value": 0.5,
            "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
            "details": [
              {
                "value": 1,
                "description": "termFreq=1.0",
                "details": []
              },
              {
                "value": 1.2,
                "description": "parameter k1",
                "details": []
              },
              {
                "value": 0.75,
                "description": "parameter b",
                "details": []
              },
              {
                "value": 1,
                "description": "avgFieldLength",
                "details": []
              },
              {
                "value": 1,
                "description": "fieldLength",
                "details": []
              }
            ]
          }
        ]
      }
    ]
  }
}</code></pre><p>Dans cet exemple, la réponse montre que le score de 1,2 est un produit de la valeur IDF (2,2) et de la valeur tfNorm (0,5). L&#x27;explication détaillée permet de comprendre les facteurs contribuant à la note et peut être utile pour affiner la pertinence de la recherche.</p><h2>Conclusion</h2><p>La notation Elasticsearch est un aspect essentiel de la fourniture de résultats de recherche pertinents. En comprenant les mécanismes de notation et en utilisant l&#x27;API Explain, vous pouvez obtenir des informations sur les facteurs affectant les résultats de recherche et optimiser vos requêtes de recherche pour une meilleure pertinence et de meilleures performances.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Modèles d'index dans Elasticsearch : Comment utiliser les modèles composables]]></title>
            <description><![CDATA[Découvrez comment créer des modèles d'index composables et de composants dans Elasticsearch pour garantir des mapping cohérents et automatiser la configuration de l'index.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/index-composable-templates</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/index-composable-templates</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 02 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/510750708df50bf79463586a1bbf35bf94acfa30-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Un index Elasticsearch peut être configuré par le biais de mappages, de paramètres et d&#x27;alias : </p><ul><li>Les définitions de mappage spécifient le schéma de données.</li><li>Les paramètres définissent la taille du groupe et les taux de rafraîchissement. </li><li>Les alias sont utilisés pour donner d&#x27;autres noms à l&#x27;index.</li></ul><p>Lorsque nous indexons un document pour la première fois ou que nous créons un index vide à l&#x27;aide de l&#x27;API Create Index, l&#x27;index est créé avec les paramètres par défaut, sans schéma de données et sans alias. Ces valeurs par défaut fonctionnent assez bien dans les environnements de développement et de test, mais il se peut que nous devions personnaliser nos indices pour les environnements de production.</p><p>L&#x27;utilisation des mappages et des paramètres par défaut en production peut entraîner des performances médiocres en matière d&#x27;indexation et de recherche. L&#x27;instanciation manuelle des indices est un processus fastidieux et chronophage. Recréer de tels index dans chaque environnement est particulièrement peu pratique si nous disposons d&#x27;un schéma de correspondance élaboré ainsi que de paramètres et d&#x27;alias personnalisés.</p><p>Heureusement, Elasticsearch met à notre disposition un outil permettant d&#x27;appliquer automatiquement une configuration prédéfinie lors de la création d&#x27;index sous la forme de <em></em> <em>modèles d&#x27; index.</em></p><h2>Modèles d&#x27;index</h2><p>Les modèles d&#x27;index nous permettent de créer des index avec une configuration définie par l&#x27;utilisateur. Lors de son instanciation, un index peut tirer la configuration de ces modèles, par exemple un nombre déterminé de shards et de réplicas ou des mappages de champs. Un modèle sera défini avec un modèle de nom et une certaine configuration. Si le nom de l&#x27;index correspond au modèle de dénomination du modèle, le nouvel index sera créé avec la configuration définie dans le modèle.</p><p>Elasticsearch a mis à jour sa fonctionnalité de modèle dans la version 7.8 avec des modèles composables. Cette nouvelle version permet de réutiliser beaucoup plus de modèles d&#x27;index, comme le montre cet article.</p><h3>Types de modèles d’index</h3><p>Les modèles d&#x27;index peuvent être classés en deux catégories :</p><ul><li><strong>Modèles d&#x27;index (ou modèles d&#x27;index composables)</strong>: Les modèles d&#x27;index composables peuvent exister seuls ou être composés d&#x27;un ou de plusieurs modèles de composants (voir la deuxième catégorie).</li><li><strong>Modèles de composants :</strong> Le modèle de composant est un modèle <em>réutilisable</em> qui définit la configuration requise. En général, le modèle de composant doit être associé à un modèle d&#x27;index. Chaque modèle de composant peut être associé à un ou plusieurs modèles d&#x27;index. </li></ul><p>Comme vous pouvez le voir dans l&#x27;image ci-dessous, les modèles d&#x27;index A et B partagent des modèles de composants (dans ce cas, un seul - le modèle 3) entre eux. Un modèle d&#x27;index peut être constitué d&#x27;un ou de plusieurs modèles de composants et chacun des modèles de composants peut être associé à un ou plusieurs modèles d&#x27;index. Les deux types de modèles peuvent exister seuls, mais les modèles de composants ne sont d&#x27;aucune utilité s&#x27;ils ne sont pas rattachés à un modèle d&#x27;index.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/96c0aac29d3992e54a79be34e14cf909e0ca2ea9-1202x556.png" style="width: 100%;" /><p>L&#x27;idée générale est de développer un catalogue de modèles de composants qu&#x27;une organisation peut utiliser pour divers besoins (par exemple, en spécifiant les différents modèles de composants pour des environnements individuels) et de les attacher à divers index via les modèles d&#x27;index composables.</p><h2>Comment créer des modèles composables (index)</h2><p>Elasticsearch fournit un point de terminaison _index_template pour gérer les modèles d&#x27;index. L&#x27;utilisateur fournit tous les mappages, paramètres et alias nécessaires ainsi qu&#x27;un modèle de nom d&#x27;index dans ce modèle. Prenons l&#x27;exemple de la création d&#x27;un modèle pour une application de microservice, <em>customer-order-service</em>, qui est responsable de la logique de génération des commandes. </p><p>Supposons que nous ayons besoin de créer un modèle pour les commandes des clients, représenté par un motif comportant des caractères génériques : *commandes. Ce modèle est censé contenir certains mappages et paramètres, tels que le champ order_date, ainsi que les numéros de shards et de réplicas.</p><p>Tout index qui est associé à ce modèle lors de sa création hérite des configurations définies dans ce modèle. Par exemple, un index black_friday_orders aura le champ order_date, les shards seront fixés à 5 et les réplicas à 2. En outre, <em>tous les</em> index créés à partir de ce modèle héritent également d&#x27;un nom d&#x27;<a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-alias/">alias</a> unique ! Créons ce modèle orders_template avec un modèle d&#x27;index défini comme *orders et avec un schéma de correspondance consistant en un seul champ oder_date avec un format de date prédéfini dd-MM-yyyy. Le code ci-dessous montre comment créer ce modèle d&#x27;index.</p><pre><code>PUT _index_template/orders_template
{
  "index_patterns": ["*orders"],
  "priority": 300,
  "template": {
    "mappings": {
      "properties": {
        "order_date": {
          "type": "date",
          "format":"dd-MM-yyyy"
        }
      }
    },
    "settings":{
      "number_of_shards":5,
      "number_of_replicas":2
    },
    "aliases":{
      "all_orders":{}
    }
  }
}</code></pre><p>Lorsque vous exécutez cette requête dans les DevTools de Kibana, le modèle est créé avec le modèle d&#x27;index *orders ainsi que le mappage prédéfini, les paramètres et un alias. L&#x27;index_patterns est un tableau de motifs de correspondance ; tout index correspondant à ce motif sera dérivé de la configuration du modèle. Vous pouvez exécuter ce qui suit pour récupérer le modèle persisté qui devrait réitérer ce que nous avons fait :</p><pre><code>GET _index_template/orders_template </code></pre><p>Il existe également une priorité, un nombre positif, définie lors de la création de l&#x27;attribut de modèle défini sur le modèle : chaque modèle est défini avec une priorité de sorte que toute modification conflictuelle provenant de différents modèles sera résolue en utilisant cette valeur, la priorité étant donnée à la valeur de priorité la plus élevée. Nous nous pencherons plus en détail sur la priorité des modèles ci-dessous.</p><h2>Création d&#x27;un index avec le modèle</h2><p>Maintenant que nous disposons d&#x27;un modèle - un schéma directeur pour la création d&#x27;index - l&#x27;étape suivante consiste à créer un index. Lorsque le nom de l&#x27;index correspond au modèle donné, les configurations modèles sont appliquées automatiquement. Pour le prouver, comme le montre le code ci-dessous, créons un tout nouvel index nommé : blackfriday_orders :</p><pre><code>PUT blackfriday_orders</code></pre><p>Comme le nom de l&#x27;index (blackfriday_orders) correspond au modèle de dénomination défini dans le modèle (c&#x27;est-à-dire *orders), l&#x27;index doit obtenir toute la configuration dérivée du modèle. Récupérons cet index fraîchement créé et vérifions si c&#x27;est bien le cas en exécutant le code suivant :</p><pre><code>GET blackfriday_orders</code></pre><p>Il doit revenir :</p><pre><code>{
  "blackfriday_orders" : {
    "aliases" : {
      "all_orders" : { }
    },
    "mappings" : {
      "properties" : {
        "order_date" : {
          "type" : "date",
          "format" : "dd-MM-yyyy"
        }
      }
    },
    "settings" : {
      "index" : {
         ...
        "number_of_shards" : "5",
        "number_of_replicas" : "2"
      }
    }
  }
}</code></pre><p>Comme l&#x27;indique la réponse, la configuration de blackfriday_orders a été héritée du modèle. Nous pouvons essayer diverses combinaisons d&#x27;indices qui hériteront avec succès de la configuration modèle :</p><pre><code>PUT blackfriday_orders
PUT americaorders
PUT cancelled--orders
PUT undefined101orders</code></pre><p>Cependant, les indices suivants n&#x27;hériteront pas de la configuration car leur nom ne correspond pas au modèle :</p><pre><code>PUT blackfriday_orders2
PUT open_orders_
PUT allorders_total</code></pre><p>Il est important de se rappeler que tous les indices dérivés d&#x27;un modèle ont le même alias - all_orders - dans ce cas. L&#x27;avantage d&#x27;un tel alias est qu&#x27;il permet d&#x27;effectuer des requêtes sur cet alias unique plutôt que sur plusieurs indices.</p><pre><code>GET blackfriday_orders,americaorders,undefined101orders/_search
GET all_orders/_search 
{
  "query": {
    "range": {
      "order_date": {
        "gte": "01-12-2021",
        "lte": "31-12-2021"
      }
    }
  }
}</code></pre><p>Si nous créons un modèle pour les *commandes, tout index correspondant est censé adopter la configuration du modèle. En général, sciemment ou non, les équipes peuvent créer quelques modèles supplémentaires pour diverses raisons. Cela signifie que le nom de l&#x27;index peut parfois correspondre à deux modèles différents ! Elasticsearch doit décider quelles configurations de ces modèles il doit appliquer. Heureusement, ce dilemme peut être résolu en utilisant le modèle de priorité.</p><h2>Comment créer des modèles de composants</h2><p>Nous avons appris à connaître les modèles d&#x27;index dans la première partie de cet article. La création de modèles avec la configuration intégrée présente quelques inconvénients, notamment le fait que la configuration n&#x27;est pas exportable pour d&#x27;autres modèles. Si nous souhaitons disposer d&#x27;une configuration similaire, par exemple pour les modèles liés aux clients (*customers), nous devrons peut-être recréer l&#x27;ensemble du modèle. Cela signifie que nous pouvons en créer des dizaines dans une organisation typique (et vous pouvez en avoir quelques autres en fonction de l&#x27;environnement).</p><p>Comme nous cherchons toujours à faciliter la réutilisation, Elasticsearch a redessiné les modèles en gardant à l&#x27;esprit la réutilisation. Les modèles de composants répondent à cette exigence. Si vous venez d&#x27;un milieu DevOps, il est fort probable que vous ayez besoin de créer des indices avec une configuration prédéfinie pour chacun des environnements. Plutôt que d&#x27;appliquer manuellement chacune de ces configurations, vous pouvez créer un modèle de composant pour chacun des environnements.</p><p>Un modèle de composant n&#x27;est rien d&#x27;autre qu&#x27;un bloc réutilisable de configurations que nous pouvons utiliser pour créer d&#x27;autres modèles d&#x27;index. Notez que les modèles de composants n&#x27;ont aucune valeur s&#x27;ils ne sont pas associés à des modèles d&#x27;index. Ils sont exposés via un point de terminaison _component_template. Voyons comment tout cela s&#x27;articule.</p><h3>Paramètres dans un modèle d’index</h3><p>Extrayons les paramètres que nous avons définis dans notre modèle d&#x27;index plus tôt et créons un modèle de composant à partir de celui-ci. Le modèle settings_component_template est censé comporter cinq shards primaires avec deux réplicas par shard primaire. La première étape, comme le montre le code ci-dessous, consiste à déclarer et à exécuter un modèle de composant avec cette configuration.</p><pre><code>PUT _component_template/settings_component_template
{
  "template":{
    "settings":{
      "number_of_shards":5,
      "number_of_replicas":2
    }
  }
}</code></pre><p>Comme le montre le code ci-dessus, nous utilisons le point de terminaison _component_template pour créer un modèle de composant. Le corps de la demande contient les informations relatives au modèle dans un objet modèle. Le modèle settings_component_template peut désormais être utilisé ailleurs dans les modèles d&#x27;index. Une différence notable est que ce modèle ne définit aucun modèle d&#x27;index ; il s&#x27;agit simplement d&#x27;un bloc de code qui configure certaines propriétés pour nous.</p><h3>Modèle de correspondance</h3><p>De la même manière, créons un autre modèle. Cette fois, nous allons extraire le schéma de correspondance que nous avons défini précédemment dans les modèles d&#x27;index autonomes. Le code ci-dessous illustre le script :</p><pre><code>PUT _component_template/mappings_component_template
{
  "template": {
    "mappings": {
      "properties": {
        "order_date": {
          "type": "date",
          "format":"dd-MM-yyyy"
        }
      }
    }
  }
}</code></pre><h3>Modèle d&#x27;alias</h3><p>Dans le même ordre d&#x27;idées, nous pouvons également avoir un modèle de composant avec les alias - deux alias (all_orders et sales_orders) :</p><pre><code>PUT _component_template/aliases_component_template
{
  "template": {
    "aliases": {
      "all_orders": {},
      "sales_orders":{}
    }
  }
}</code></pre><h3>Modèle d&#x27;index composable</h3><p>Maintenant que nous disposons de ces trois modèles de composants, la prochaine étape consiste à les utiliser. Nous pouvons le faire en laissant un modèle d&#x27;index pour, par exemple, christmas_orders, l&#x27;utiliser :</p><pre><code>PUT _index_template/composed_orders_template
{
  "index_patterns": [
    "*orders"
  ],
  "priority": 500,
  "composed_of": [
    "settings_component_template",
    "mappings_component_template",
    "aliases_component_template"
  ]
}</code></pre><p>La balise composed_of est une collection de tous les modèles de composants qui constituent ce modèle. Dans ce cas, nous choisissons les paramètres, les mappings et les alias des modèles de composants. Nous avons également relevé le niveau de priorité afin que ce modèle l&#x27;emporte sur tous les autres. Une fois le modèle prêt, tous les indices correspondant au motif *orders hériteront de la configuration de ces trois modèles de composants.</p><p>Cela dit, si nous souhaitons créer un nouveau modèle, par exemple clients, avec un seul des modèles existants (settings_component_template) et un modèle alias nouvellement créé (aliases_component_template - voir ci-dessous), nous pouvons le faire à l&#x27;aide de :</p><pre><code>PUT _component_template/aliases_component_template2
{
  "template": {
    "aliases": {
      "all_customers": {}
    }
  }
}</code></pre><p>Le modèle d&#x27;index se présente comme suit :</p><pre><code>PUT _index_template/composed_customers_template
{
  "index_patterns": [
    "*customers*"
  ],
  "priority": 200,
  "composed_of": [
    "settings_component_template",
    "aliases_component_template2"
  ]
}</code></pre><p>Avez-vous vu que le modèle settings_component_template a été (ré)utilisé dans deux modèles différents ? C&#x27;est la force des modèles de composants.</p><h2>Priorité du modèle d’index</h2><p>Il est possible que les développeurs créent plusieurs modèles d&#x27;index sans tenir compte du stock existant. Il est important de définir une priorité pour chacun de ces modèles afin que celui qui a la priorité la plus élevée soit utilisé. Par exemple, le modèle my_orders_template_1 remplace le modèle my_orders_template_2 dans l&#x27;extrait de code suivant :</p><pre><code>PUT _index_template/my_orders_template_1
{
  "index_patterns": ["*orders"],
  "priority": 1000,
  "template": { ... }
}
PUT _index_template/my_orders_template2
{
  "index_patterns": ["*orders"],
  "priority": 300,
  "template": { ... }
}</code></pre><p>Lorsque plusieurs modèles correspondent aux index créés, Elasticsearch applique toutes les configurations de tous les modèles correspondants, mais remplace tout ce qui a une priorité plus élevée.</p><h2>Préséance des modèles</h2><p>Enfin, vous vous interrogez peut-être sur la préséance des modèles : la configuration définie dans le modèle de composant est-elle prioritaire par rapport à celle définie dans le modèle d&#x27;index principal lui-même ? Ou l&#x27;inverse ? Il y a des règles à respecter :</p><ul><li>Un index créé avec des configurations explicites a la priorité sur tout le reste - cela signifie que si vous créez un index avec des configurations explicites, ne vous attendez pas à ce qu&#x27;elles soient remplacées par les modèles.</li><li>Les anciens modèles (modèles créés avant la version 7.8) ont une priorité inférieure à celle des modèles composables.</li></ul><h2>Résumé</h2><ul><li>Un index contient des mappings, des paramètres et des alias : les mappings définissent le schéma des champs, les paramètres définissent les paramètres de l&#x27;index tels que le nombre de shards et de réplicas, et les alias donnent des noms alternatifs à l&#x27;index.</li><li>Les modèles permettent de créer des indices avec des configurations prédéfinies. Le fait de nommer un index avec un nom qui correspond au modèle d&#x27;index défini dans un modèle spécifique configurera automatiquement cet index conformément au modèle.</li><li>Elasticsearch a introduit les modèles d&#x27;index composables dans la version 7.8. Les modèles d&#x27;index composables permettent la modularité et le changement de version des modèles.</li><li>Les modèles composables sont constitués d&#x27;au moins un modèle de composant.</li><li>Un modèle d&#x27;index peut également avoir sa propre configuration.</li><li>Un modèle de composant est un modèle réutilisable avec une configuration prédéfinie, tout comme un modèle d&#x27;index composable.</li><li>Toutefois, les modèles de composants sont censés faire partie d&#x27;un modèle d&#x27;index ; ils sont inutiles s&#x27;ils ne sont pas &quot;composés&quot; dans un modèle d&#x27;index.</li><li>Les modèles de composants n&#x27;ont pas de modèle d&#x27;index défini, ce qui est une autre raison pour laquelle ils sont &quot;censés&quot; faire partie d&#x27;un modèle d&#x27;index.</li><li>Chaque modèle a une priorité - un nombre positif. Plus le nombre est élevé, plus l&#x27;application du modèle est prioritaire.</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Recherche Elasticsearch par deux champs]]></title>
            <description><![CDATA[Explorez les techniques de recherche par deux champs, y compris les requêtes multi-match, les requêtes booléennes et le renforcement de champ au moment de la requête.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-search-by-two-fields</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-search-by-two-fields</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 30 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5d04bbcfc3925f48f3487ea4c7e0dd2205316d0-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La recherche sur plusieurs champs dans Elasticsearch est une exigence courante dans de nombreuses applications. Dans cet article, nous allons explorer des techniques avancées permettant d&#x27;effectuer des recherches sur deux champs, notamment les requêtes à correspondances multiples, les requêtes bool et l&#x27;augmentation du nombre de champs au moment de la requête. Ces techniques vous aideront à créer des résultats de recherche plus précis et plus pertinents pour vos utilisateurs.</p><h2>Techniques avancées pour effectuer des recherches sur deux champs</h2><h3>1. Requête multiple</h3><p>Une requête multiple vous permet de rechercher une seule chaîne de caractères dans plusieurs champs. Ceci est utile lorsque vous souhaitez trouver des documents qui contiennent la chaîne de requête donnée dans l&#x27;un ou l&#x27;autre des deux champs. Voici un exemple de requête multi-correspondance recherchant le terme &quot;exemple&quot; dans les champs &quot;titre&quot; ou &quot;description&quot; :</p><pre><code>{
  "query": {
    "multi_match": {
      "query": "example",
      "fields": ["title", "description"]
    }
  }
}</code></pre><h3>2. Requête Bool</h3><p>Une requête bool vous permet de combiner plusieurs requêtes en utilisant la logique booléenne. Vous pouvez utiliser la clause &quot;devrait&quot; pour rechercher les documents qui correspondent à la requête dans l&#x27;un ou l&#x27;autre des deux champs. Voici un exemple de requête bool recherchant le terme &quot;exemple&quot; dans les champs &quot;titre&quot; et &quot;description&quot; :</p><pre><code>{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": "example"}},
        {"match": {"description": "example"}}
      ]
    }
  }
}</code></pre><h3>3. Renforcement des champs au moment de la requête</h3><p>Il peut arriver que vous souhaitiez accorder plus d&#x27;importance à un champ qu&#x27;à un autre lors de la recherche. Vous pouvez y parvenir en appliquant un facteur d&#x27;amplification au champ au moment de la requête. Une valeur de boost plus élevée donne plus de poids au champ, ce qui le rend plus susceptible d&#x27;influencer le résultat final de la recherche. Voici un exemple de requête à correspondances multiples avec un facteur d&#x27;amplification appliqué au champ &quot;titre&quot; :</p><pre><code>{
  "query": {
    "multi_match": {
      "query": "example",
      "fields": ["title^3", "description"]
    }
  }
}</code></pre><p>Dans cet exemple, le champ &quot;titre&quot; a un facteur d&#x27;amplification de 3, ce qui signifie qu&#x27;il est trois fois plus important que le champ &quot;description&quot; pour déterminer le score de recherche.</p><h3>4. Combinaison de requêtes avec différents facteurs d&#x27;amplification</h3><p>Vous pouvez également combiner plusieurs requêtes avec différents facteurs d&#x27;augmentation à l&#x27;aide d&#x27;une requête bool. Cela vous permet d&#x27;affiner l&#x27;importance de chaque champ dans les résultats de la recherche. Voici un exemple de requête bool avec différents facteurs de boost appliqués aux champs &quot;titre&quot; et &quot;description&quot; :</p><pre><code>{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": {"query": "example", "boost": 3}}},
        {"match": {"description": {"query": "example", "boost": 1}}}
      ]
    }
  }
}</code></pre><p>Dans cet exemple, le champ &quot;titre&quot; a un facteur d&#x27;amplification de 3, tandis que le champ &quot;description&quot; a un facteur d&#x27;amplification de 1.</p><h2>Conclusion</h2><p>La recherche par deux champs dans Elasticsearch peut être réalisée à l&#x27;aide de techniques avancées telles que les requêtes à correspondances multiples, les requêtes bool et l&#x27;augmentation du nombre de champs au moment de la requête. En combinant ces techniques, vous pouvez créer des résultats de recherche plus précis et plus pertinents pour vos utilisateurs. Expérimentez différentes combinaisons de requêtes et de facteurs de stimulation pour trouver la configuration de recherche optimale pour votre cas d&#x27;utilisation spécifique.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[​​Construire un flux de travail RAG en utilisant LangGraph et Elasticsearch]]></title>
            <description><![CDATA[Apprenez à configurer et à personnaliser un modèle d'agent de récupération LangGraph avec Elasticsearch afin de créer un flux de travail RAG pour une récupération efficace des données et des réponses basées sur l'IA.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/build-rag-workflow-langgraph-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/build-rag-workflow-langgraph-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 25 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/17b7e7b336f73e232375d1add582ae5f6c52a279-1440x840.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Le <a href="https://github.com/langchain-ai/retrieval-agent-template">modèle d&#x27;agent de recherche LangGraph</a> est un projet de démarrage développé par LangChain pour faciliter la création de systèmes de réponse aux questions basés sur la recherche en utilisant LangGraph dans LangGraph Studio. Ce modèle est préconfiguré pour s&#x27;intégrer de manière transparente à Elasticsearch, ce qui permet aux développeurs de créer rapidement des agents capables d&#x27;indexer et d&#x27;extraire des documents de manière efficace.</p><p>Ce blog se concentre sur l&#x27;exécution et la personnalisation du modèle d&#x27;agent de récupération LangChain en utilisant LangGraph Studio et LangGraph CLI. Le modèle fournit un cadre pour la construction d&#x27;applications de génération augmentée de recherche (RAG), en tirant parti de divers backends de recherche tels qu&#x27;Elasticsearch.</p><p>Nous vous guiderons dans la mise en place, la configuration de l&#x27;environnement et l&#x27;exécution efficace du modèle avec Elastic tout en personnalisant le flux de l&#x27;agent.</p><h2>Produits requis</h2><p>Avant de poursuivre, assurez-vous que les éléments suivants sont installés :</p><ul><li>Déploiement d&#x27;Elasticsearch Cloud ou déploiement d&#x27;Elasticsearch sur site (ou créer un <a href="https://www.elastic.co/fr/cloud/cloud-trial-overview">essai gratuit de </a>14 jours sur Elastic Cloud) - Version 8.0.0 ou supérieure</li><li>Python 3.9+</li><li>Accès à un fournisseur de LLM tel que <a href="https://cohere.com/">Cohere</a> (utilisé dans ce guide), <a href="https://openai.com/">OpenAI</a>, ou <a href="https://www.anthropic.com/claude">Anthropic/Claude</a></li></ul><h2>Création de l&#x27;application LangGraph</h2><h3>1. Installer l&#x27;interface de programmation LangGraph</h3><pre><code>pip install --upgrade "langgraph-cli[inmem]"</code></pre><h3>2. Créer une application LangGraph à partir de retrieval-agent-template</h3><pre><code>mkdir lg-agent-demo
cd lg-agent-demo
langgraph new lg-agent-demo </code></pre><p><em>Un menu interactif vous permettra de choisir parmi une liste de modèles disponibles. </em>Sélectionnez 4 pour Retrieval Agent et 1 pour Python, comme indiqué ci-dessous :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6a41a41f95c2477c67810adc7be46d91faf06878-1600x407.png" style="width: 100%;" /><ul><li><strong>Dépannage</strong>: Si vous rencontrez l&#x27;erreur &quot;urllib.error.URLError : &lt;urlopen error [SSL : CERTIFICATE_VERIFY_FAILED] certificate verify failed : unable to get local issuer certificate (_ssl.c:1000)&gt; &quot;</li></ul><p>Veuillez exécuter la commande Install Certificate de Python pour résoudre le problème, comme indiqué ci-dessous.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/83ec238136c41738457299fd42c83aff32eb5b97-1407x75.png" style="width: 100%;" /><h3>3. Installer les dépendances</h3><p>À la racine de votre nouvelle application LangGraph, créez un environnement virtuel et installez les dépendances en mode <code>edit</code> afin que vos modifications locales soient utilisées par le serveur :</p><pre><code>#For Mac
python3 -m venv lg-demo
source lg-demo/bin/activate 
pip install -e .

#For Windows
python3 -m venv lg-demo
lg-demo\Scripts\activate 
pip install -e .</code></pre><h2>Mise en place de l&#x27;environnement</h2><h3>1. Créez un environnement . fichier</h3><p>Le fichier <code>.env</code> contient les clés API et les configurations permettant à l&#x27;application de se connecter au fournisseur de LLM et de récupération que vous avez choisi. Générer un nouveau fichier <code>.env</code> en dupliquant l&#x27;exemple de configuration :</p><pre><code>cp .env.example .env</code></pre><h3>2. Configurez le fichier .env fichier</h3><p>Le fichier <code>.env</code> est livré avec un ensemble de configurations par défaut. Vous pouvez le mettre à jour en ajoutant les clés et valeurs API nécessaires en fonction de votre configuration. Les clés qui ne sont pas pertinentes pour votre cas d&#x27;utilisation peuvent être laissées inchangées ou supprimées.</p><pre><code># To separate your traces from other applications
LANGSMITH_PROJECT=retrieval-agent

# LLM choice (set the API key for your selected provider):
ANTHROPIC_API_KEY=your_anthropic_api_key
FIREWORKS_API_KEY=your_fireworks_api_key
OPENAI_API_KEY=your_openai_api_key

# Retrieval provider (configure based on your chosen service):

## Elastic Cloud:
ELASTICSEARCH_URL=https://your_elastic_cloud_url
ELASTICSEARCH_API_KEY=your_elastic_api_key

## Elastic Local:
ELASTICSEARCH_URL=http://host.docker.internal:9200
ELASTICSEARCH_USER=elastic
ELASTICSEARCH_PASSWORD=changeme

## Pinecone:
PINECONE_API_KEY=your_pinecone_api_key
PINECONE_INDEX_NAME=your_pinecone_index_name

## MongoDB Atlas:
MONGODB_URI=your_mongodb_connection_string

# Cohere API key:
COHERE_API_KEY=your_cohere_api_key</code></pre><ul><li>Exemple de fichier <code>.env</code> (avec Elastic Cloud et Cohere)</li></ul><p>Vous trouverez ci-dessous un exemple de configuration <code>.env</code> pour l&#x27;utilisation d&#x27;<strong>Elastic Cloud</strong> en tant que fournisseur d&#x27;extraction et de <strong>Cohere</strong> en tant que LLM, comme démontré dans ce blog :</p><pre><code># To separate your traces from other applications
LANGSMITH_PROJECT=retrieval-agent
#Retrieval Provider
# Elasticsearch configuration
ELASTICSEARCH_URL=elastic-url:443
ELASTICSEARCH_API_KEY=elastic_api_key
# Cohere API key
COHERE_API_KEY=cohere_api_key</code></pre><p><em>Note : Bien que ce guide utilise Cohere pour la génération de réponses et l&#x27;intégration, vous êtes libre d&#x27;utiliser d&#x27;autres fournisseurs LLM tels que <strong>OpenAI</strong>, <strong>Claude</strong>, ou même un modèle LLM local en fonction de votre cas d&#x27;utilisation. Assurez-vous que chaque clé que vous avez l&#x27;intention d&#x27;utiliser est présente et correctement définie dans le  fichier<code>.env</code></em>.</p><h3>3. Mise à jour du fichier de configuration -configuration.py </h3><p>Après avoir configuré votre fichier <code>.env</code> avec les clés API appropriées, l&#x27;étape suivante consiste à mettre à jour la configuration du modèle par défaut de votre application. La mise à jour de la configuration garantit que le système utilise les services et les modèles que vous avez spécifiés dans votre fichier <code>.env</code>.</p><p>Accédez au fichier de configuration :</p><pre><code> cd src/retrieval_graph</code></pre><p>Le fichier <code>configuration.py</code> contient les paramètres du modèle par défaut utilisés par l&#x27;agent de recherche pour trois tâches principales :</p><ul><li><strong>Modèle d&#x27;intégration</strong> - convertit les documents en représentations vectorielles</li><li><strong>Modèle de requête</strong> - traite la requête de l&#x27;utilisateur en un vecteur</li><li><strong>Modèle de réponse</strong> - génère la réponse finale</li></ul><p>Par défaut, le code utilise les modèles d&#x27;<strong>OpenAI</strong> (par exemple, <code>openai/text-embedding-3-small</code>) et d&#x27;<strong>Anthropic</strong> (par exemple, <code>anthropic/claude-3-5-sonnet-20240620 and anthropic/claude-3-haiku-20240307</code>).<br/><br/>Dans ce blog, nous passerons à l&#x27;utilisation des modèles Cohere. Si vous utilisez déjà OpenAI ou Anthropic, aucun changement n&#x27;est nécessaire.</p><h4>Exemple de modifications (en utilisant Cohere) :</h4><p>Ouvrez <code>configuration.py</code> et modifiez les valeurs par défaut du modèle comme indiqué ci-dessous :</p><pre><code>…
 embedding_model: Annotated[
       str,
       {"__template_metadata__": {"kind": "embeddings"}},
   ] = field(
       default="cohere/embed-english-v3.0",
…
response_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
       default="cohere/command-r-08-2024",
…
query_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
       default="cohere/command-r-08-2024",
       metadata={</code></pre><h2>Exécution de l&#x27;agent de recherche avec LangGraph CLI</h2><h3>1. Lancer le serveur LangGraph</h3><pre><code>cd lg-agent-demo
langgraph dev</code></pre><p>Cette opération permet de démarrer localement le serveur LangGraph API. Si l&#x27;opération se déroule correctement, vous devriez voir apparaître quelque chose comme :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e3c3344b24651067e2d0892d870feca505b3be35-1494x542.png" style="width: 100%;" /><p>URL de l&#x27;interface utilisateur d&#x27;Open Studio.</p><p>Deux graphiques sont disponibles :</p><ul><li><strong>Graphique de récupération</strong>: Récupère les données d&#x27;Elasticsearch et répond à la requête à l&#x27;aide d&#x27;un LLM.</li><li><strong>Graphique d&#x27;indexation</strong>: Indexe les documents dans Elasticsearch et génère des embeddings à l&#x27;aide d&#x27;un LLM.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99b579ca44b2d13f61da7e1de5886ea5b9a5e16f-1600x1009.png" style="max-width:100%; margin-bottom:1em;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f54d182d2789b6a4d113b0c88063531ea0b6de5d-1600x1009.png" style="max-width:100%; margin-bottom:1em;" /><h3>2. Configuration du graphe de l&#x27;indexeur</h3><ul><li>Ouvrez le graphique de l&#x27;indexeur.</li><li>Cliquez sur gérer les assistants.<ul><li>Cliquez sur <strong>&quot;Ajouter un nouvel assistant</strong>&quot;, entrez les détails de l&#x27;utilisateur comme spécifié, puis fermez la fenêtre.</li></ul></li></ul><pre><code>{"user_id": "101"}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f87958e40760ac9671639f4995962bcea090991-1600x1139.png" style="max-width:100%; margin-bottom:1em;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f802f6b65b3f06d7d1a521c3f40385c6006413b-1422x1212.png" style="max-width:100%; margin-bottom:1em;" /><h3>3. Indexation de documents types</h3><ul><li>Indexez les documents types suivants, qui représentent un rapport trimestriel hypothétique pour l&#x27;organisation NoveTech :</li></ul><pre><code>[
  {    "page_content": "NoveTech Solutions Q1 2025 Report - Revenue: $120.5M, Net Profit: $18.2M, EPS: $2.15. Strong AI software launch and $50M government contract secured."
  },
  {
    "page_content": "NoveTech Solutions Business Highlights - AI-driven analytics software gained 15% market share. Expansion into Southeast Asia with two new offices. Cloud security contract secured."
  },
  {
    "page_content": "NoveTech Solutions Financial Overview - Operating expenses at $85.3M, Gross Margin 29.3%. Stock price rose from $72.5 to $78.3. Market Cap reached $5.2B."
  },
  {
    "page_content": "NoveTech Solutions Challenges - Rising supply chain costs impacting hardware production. Regulatory delays slowing European expansion. Competitive pressure in cybersecurity sector."
  },
  {
    "page_content": "NoveTech Solutions Future Outlook - Expected revenue for Q2 2025: $135M. New AI chatbot and blockchain security platform launch planned. Expansion into Latin America."
  },
  {
    "page_content": "NoveTech Solutions Market Performance - Year-over-Year growth at 12.7%. Stock price increase reflects investor confidence. Cybersecurity and AI sectors remain competitive."
  },
  {
    "page_content": "NoveTech Solutions Strategic Moves - Investing in R&D to enhance AI-driven automation. Strengthening partnerships with enterprise cloud providers. Focusing on data privacy solutions."
  },
  {
    "page_content": "NoveTech Solutions CEO Statement - 'NoveTech Solutions continues to innovate in AI and cybersecurity. Our growth strategy remains strong, and we foresee steady expansion in the coming quarters.'"
  }
]</code></pre><p>Une fois les documents indexés, un message de suppression apparaît dans le fil de discussion, comme indiqué ci-dessous.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fd3a1efd64cb54d54ea56ef5055249dd066d5708-1600x854.png" style="width: 100%;" /><h3>4. Exécution du graphe de recherche</h3><ul><li>Passez au graphique de recherche.</li><li>Saisissez la requête de recherche suivante :</li></ul><pre><code>What was NovaTech Solutions total revenue in Q1 2025?</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb4d8ddfe0effd7e1868fba921b8ef13f7baf27a-1600x755.png" style="width: 100%;" /><p>Le système renvoie les documents pertinents et fournit une réponse exacte sur la base des données indexées.</p><h2>Personnaliser l&#x27;agent de recherche</h2><p>Pour améliorer l&#x27;expérience de l&#x27;utilisateur, nous introduisons une étape de personnalisation dans le graphe de recherche afin de prédire les trois prochaines questions que l&#x27;utilisateur pourrait poser. Cette prédiction est basée sur :</p><ul><li>Contexte des documents extraits</li><li>Interactions avec les utilisateurs précédents</li><li>Dernière requête de l&#x27;utilisateur</li></ul><p>Les modifications de code suivantes sont nécessaires pour mettre en œuvre la fonction de prédiction des requêtes :</p><h3>1. Mettez à jour graph.py</h3><ul><li>Ajouter la fonction <code>predict_query</code>:</li></ul><pre><code>async def predict_query(
   state: State, *, config: RunnableConfig
) -> dict[str, list[BaseMessage]]:
   logger.info(f"predict_query predict_querypredict_query predict_query predict_query predict_query")  # Log the query

   configuration = Configuration.from_runnable_config(config)
   prompt = ChatPromptTemplate.from_messages(
       [
           ("system", configuration.predict_next_question_prompt),
           ("placeholder", "{messages}"),
       ]
   )
   model = load_chat_model(configuration.response_model)
   user_query = state.queries[-1] if state.queries else "No prior query available"
   logger.info(f"user_query: {user_query}")
   logger.info(f"statemessage: {state.messages}")
   #human_messages = [msg for msg in state.message if isinstance(msg, HumanMessage)]

   message_value = await prompt.ainvoke(
       {
           "messages": state.messages,
           "user_query": user_query,  # Use the most recent query as primary input
           "system_time": datetime.now(tz=timezone.utc).isoformat(),
       },
       config,
   )

   next_question = await model.ainvoke(message_value, config)
   return {"next_question": [next_question]}</code></pre><ul><li>Modifier la fonction <code>respond</code> pour qu&#x27;elle renvoie l&#x27;objet <strong><code>response</code></strong> au lieu du message :</li></ul><pre><code>async def respond(
   state: State, *, config: RunnableConfig
) -> dict[str, list[BaseMessage]]:
   """Call the LLM powering our "agent"."""
   configuration = Configuration.from_runnable_config(config)
   # Feel free to customize the prompt, model, and other logic!
   prompt = ChatPromptTemplate.from_messages(
       [
           ("system", configuration.response_system_prompt),
           ("placeholder", "{messages}"),
       ]
   )
   model = load_chat_model(configuration.response_model)

   retrieved_docs = format_docs(state.retrieved_docs)
   message_value = await prompt.ainvoke(
       {
           "messages": state.messages,
           "retrieved_docs": retrieved_docs,
           "system_time": datetime.now(tz=timezone.utc).isoformat(),
       },
       config,
   )
   response = await model.ainvoke(message_value, config)
   # We return a list, because this will get added to the existing list
   return {"response": [response]}</code></pre><ul><li>Mise à jour de la structure du graphe afin d&#x27;ajouter un nouveau nœud et une nouvelle arête pour predict_query :</li></ul><pre><code>builder.add_node(generate_query)
builder.add_node(retrieve)
builder.add_node(respond)
builder.add_node(predict_query)
builder.add_edge("__start__", "generate_query")
builder.add_edge("generate_query", "retrieve")
builder.add_edge("retrieve", "respond")
builder.add_edge("respond", "predict_query")</code></pre><h3>2. Mettez à jour prompts.py</h3><ul><li>Demande de devis pour la prédiction de guery dans <code>prompts.py</code>:</li></ul><pre><code>PREDICT_NEXT_QUESTION_PROMPT = """Given the user query and the retrieved documents, suggest the most likely next question the user might ask.

**Context:**
- Previous Queries:
{previous_queries}

- Latest User Query: {user_query}

- Retrieved Documents:
{retrieved_docs}

**Guidelines:**
1. Do not suggest a question that has already been asked in previous queries.
2. Consider the retrieved documents when predicting the next logical question.
3. If the user's query is already fully answered, suggest a relevant follow-up question.
4. Keep the suggested question natural and conversational.
5. Suggest at least 3 question

System time: {system_time}"""</code></pre><h3>3. Mettez à jour configuration.py</h3><ul><li>Ajouter <code>predict_next_question_prompt</code>:</li></ul><pre><code>predict_next_question_prompt: str = field(
       default=prompts.PREDICT_NEXT_QUESTION_PROMPT,
       metadata={"description": "The system prompt used for generating responses."},
   )</code></pre><h3>4. Mettez à jour state.py</h3><ul><li>Ajouter les attributs suivants :</li></ul><pre><code>response: Annotated[Sequence[AnyMessage], add_messages]
next_question : Annotated[Sequence[AnyMessage], add_messages]</code></pre><h3>5. Réexécutez le graphique de recherche</h3><ul><li>Saisissez à nouveau la requête de recherche suivante :</li></ul><pre><code>What was NovaTech Solutions total revenue in Q1 2025?</code></pre><p>Le système traitera les données et prévoira trois questions connexes que les utilisateurs pourraient poser, comme indiqué ci-dessous.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/70e855a2e4edc0ba5a147588df0de30eb081d053-1600x777.png" style="width: 100%;" /><h2>Conclusion</h2><p>L&#x27;intégration du modèle Retrieval Agent dans LangGraph Studio et CLI offre plusieurs avantages :</p><ul><li><strong>Développement accéléré</strong>: Les modèles et les outils de visualisation rationalisent la création et le débogage des flux de recherche, réduisant ainsi le temps de développement.</li><li><strong>Déploiement transparent</strong>: La prise en charge intégrée des API et de la mise à l&#x27;échelle automatique garantit un déploiement sans heurts dans tous les environnements.</li><li><strong>Des mises à jour faciles :</strong> La modification des flux de travail, l&#x27;ajout de nouvelles fonctionnalités et l&#x27;intégration de nœuds supplémentaires sont simples, ce qui facilite l&#x27;évolution et l&#x27;amélioration du processus de recherche.</li><li><strong>Mémoire persistante</strong>: Le système conserve les états et les connaissances des agents, ce qui améliore la cohérence et la fiabilité.</li><li><strong>Modélisation flexible des flux de travail</strong>: Les développeurs peuvent personnaliser la logique de recherche et les règles de communication pour des cas d&#x27;utilisation spécifiques.</li><li><strong>Interaction et débogage en temps réel</strong>: La possibilité d&#x27;interagir avec les agents en cours d&#x27;exécution permet de tester et de résoudre les problèmes de manière efficace.</li></ul><p>En tirant parti de ces fonctionnalités, les organisations peuvent construire des systèmes de recherche puissants, efficaces et évolutifs qui améliorent l&#x27;accessibilité des données et l&#x27;expérience de l&#x27;utilisateur.</p><p>Le code source complet de ce projet est disponible sur <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/langraph-retrieval-agent-template-demo">GitHub</a>.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/bbq-implementation-into-use-case</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/bbq-implementation-into-use-case</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
            <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/66842055367cdd795532b01c167f2a4b03dc65e3-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La recherche vectorielle constitue la base de la mise en œuvre d&#x27;une recherche sémantique pour le texte ou d&#x27;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&#x27;utilisation ?</h2><blockquote>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 &quot;Apprentissage complémentaire&quot;</a> ci-dessous. Dans le cadre de ce blog, l&#x27;accent est mis sur la mise en œuvre.</blockquote><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&#x27;agit d&#x27;une question de compression, car il s&#x27;avère qu&#x27;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&#x27;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&#x27;esprit :</p><ul><li>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.<ul><li>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.</li></ul></li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/590f3b3c72a76023a38a0436cd9ff90a9f80e936-1964x1262.png" style="width: 100%;" /><ul><li>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.<ul><li>L&#x27;idéal est de pouvoir charger tous les vecteurs stockés dans la mémoire.</li></ul></li><li>Les modèles d&#x27;intégration produisent généralement des vecteurs avec une précision float32, soit 4 octets par nombre à virgule flottante.</li><li>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.</li></ul><p>Si l&#x27;on prend cela pour acquis, on constate qu&#x27;un problème se pose rapidement dès que l&#x27;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 &quot;<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>&quot; 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>Si vous utilisez Elastic Cloud ou on-prem, vous aurez besoin d&#x27;une version d&#x27;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.</li><li>En outre, vous devrez également vous assurer qu&#x27;il existe un <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.18/ml-settings.html">nœud d&#x27;apprentissage automatique (ML)</a> dans votre cluster. (Remarque : un nœud ML doté d&#x27;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).</li><li>Si vous utilisez Serverless, vous devrez sélectionner une instance optimisée pour les vecteurs.</li><li>Vous aurez également besoin d&#x27;une connaissance de base des bases de données vectorielles. Si vous n&#x27;êtes pas encore familiarisé avec les concepts de recherche vectorielle dans Elastic, vous pouvez consulter les ressources suivantes :<ul><li><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></li><li><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></li></ul></li></ul><h2>Meilleure implémentation de la quantification binaire (BBQ)</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4d388078495566f0527e931e0c2e38facdce83c6-1503x748.png" style="width: 100%;" /><p>Pour que ce blog reste simple, vous utiliserez les fonctions intégrées lorsqu&#x27;elles sont disponibles. Dans ce cas, vous disposez du modèle d&#x27;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&#x27;exécutera directement dans Elasticsearch sur un nœud d&#x27;apprentissage automatique. Notez que vous pouvez remplacer le modèle <code>text_embedding</code> par l&#x27;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&#x27;autres). Si le modèle que vous préférez n&#x27;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&#x27;abord, vous devrez créer un point final d&#x27;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&#x27;il n&#x27;existe pas encore, il configurera votre point d&#x27;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><pre><code>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
    }
  }
}</code></pre><p>Une fois qu&#x27;il est revenu, votre modèle est configuré et vous pouvez tester que le modèle fonctionne comme prévu à l&#x27;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><pre><code>POST _inference/text_embedding/my_e5_model
{
  "input": "my awesome piece of text"
}</code></pre><p>Si vous rencontrez des problèmes liés au fait que votre modèle formé n&#x27;est affecté à aucun nœud, il se peut que vous deviez démarrer votre modèle manuellement.</p><pre><code>POST _ml/trained_models/.multilingual-e5-small/deployment/_start</code></pre><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&#x27;intégration. Vous pouvez également passer outre l&#x27;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><pre><code>PUT bbq-my-byte-quantized-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "bbq_hnsw"
        }
      }
    }
  }
}</code></pre><p>Pour s&#x27;assurer qu&#x27;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&#x27;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&#x27;ingestion d&#x27;inférence, qui utilise le <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/inference-apis.html">service d&#x27;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><pre><code>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"
      }
    }
  ]
}</code></pre><p>Vous êtes maintenant prêt à ajouter des documents à l&#x27;aide des deux premières commandes ci-dessous et à tester le fonctionnement de vos recherches à l&#x27;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><pre><code>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"
  ]
}</code></pre><p>Comme nous l&#x27;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&#x27;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><pre><code>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"
  ]
}</code></pre><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><pre><code>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"
  ]
}</code></pre><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&#x27;on travaille avec la recherche vectorielle. La décomposition suivante illustre comment les différentes techniques de quantification réduisent considérablement l&#x27;empreinte mémoire des données vectorielles.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Vecteurs (V)</td><td style="border: 1px solid #ccc; padding: 4px;">Dimensions (D)</td><td style="border: 1px solid #ccc; padding: 4px;">brut (V x D x 4)</td><td style="border: 1px solid #ccc; padding: 4px;">int8 (V x (D x 1 + 4))</td><td style="border: 1px solid #ccc; padding: 4px;">int4 (V x (D x 0,5 + 4))</td><td style="border: 1px solid #ccc; padding: 4px;">bbq (V x (D x 0,125 + 4))</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10,000,000</td><td style="border: 1px solid #ccc; padding: 4px;">384</td><td style="border: 1px solid #ccc; padding: 4px;">14.31GB</td><td style="border: 1px solid #ccc; padding: 4px;">3.61GB</td><td style="border: 1px solid #ccc; padding: 4px;">1.83GB</td><td style="border: 1px solid #ccc; padding: 4px;">0.58GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">50,000,000</td><td style="border: 1px solid #ccc; padding: 4px;">384</td><td style="border: 1px solid #ccc; padding: 4px;">71.53GB</td><td style="border: 1px solid #ccc; padding: 4px;">18.07GB</td><td style="border: 1px solid #ccc; padding: 4px;">9.13GB</td><td style="border: 1px solid #ccc; padding: 4px;">2.89GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">100,000,000</td><td style="border: 1px solid #ccc; padding: 4px;">384</td><td style="border: 1px solid #ccc; padding: 4px;">143.05GB</td><td style="border: 1px solid #ccc; padding: 4px;">36.14GB</td><td style="border: 1px solid #ccc; padding: 4px;">18.25GB</td><td style="border: 1px solid #ccc; padding: 4px;">5.77GB</td>
                    </tr>
              </tbody>
            </table>
          <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&#x27;effectuer des recherches efficaces dans les données et de faire évoluer vos flux de travail d&#x27;IA pour accélérer les recherches et optimiser le stockage des données.</p><h2>Poursuite de l&#x27;apprentissage</h2><p>Si vous souhaitez en savoir plus sur le barbecue, n&#x27;hésitez pas à consulter les ressources suivantes :</p><ul><li><a href="https://www.elastic.co/fr/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Quantification binaire (BBQ) dans Lucene et Elasticsearch</a></li><li><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></li><li><a href="https://www.elastic.co/fr/search-labs/blog/optimized-scalar-quantization-elasticsearch">Quantification scalaire optimisée : Une quantification binaire encore meilleure</a></li><li><a href="https://www.youtube.com/watch?v=04NzMt2Nigc">Meilleure quantification binaire (BBQ) : De l&#x27;octet au BBQ, le secret d&#x27;une meilleure recherche vectorielle par Ben Trent</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Utilisation de la taille du tas d'Elasticsearch et collecte des déchets de la JVM]]></title>
            <description><![CDATA[Exploration de l'utilisation de la taille du tas d'Elasticsearch et de la collecte des déchets de la JVM, y compris les meilleures pratiques et la manière de résoudre les problèmes lorsque l'utilisation de la mémoire du tas est trop élevée ou lorsque les performances de la JVM ne sont pas optimales.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-heap-size-jvm-garbage-collection</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-heap-size-jvm-garbage-collection</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b162c28623b9070fd1980bcd891b9dd1e868f2f0-720x421.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La taille du tas est la quantité de RAM allouée à la machine virtuelle Java d&#x27;un nœud Elasticsearch.</p><p>Depuis la version 7.11, Elasticsearch définit par défaut automatiquement la taille du tas de la JVM en fonction des rôles et de la mémoire totale d&#x27;un nœud. L&#x27;utilisation du dimensionnement par défaut est recommandée pour la plupart des environnements de production. Toutefois, si vous souhaitez définir manuellement la taille du tas de votre JVM, vous devez en règle générale définir -Xms et -Xmx sur la MÊME valeur, soit 50% de votre RAM totale disponible, avec un maximum (approximatif) de 31 Go.</p><p>Une taille de tas plus importante permet à votre nœud de disposer de plus de mémoire pour les opérations d&#x27;indexation et de recherche. Cependant, votre nœud a également besoin de mémoire pour la mise en cache, de sorte que l&#x27;utilisation de 50% maintient un équilibre sain entre les deux. Pour cette même raison, en production, vous devez éviter d&#x27;utiliser d&#x27;autres processus gourmands en mémoire sur le même nœud qu&#x27;Elasticsearch.</p><p>En règle générale, l&#x27;utilisation du tas suit un schéma en dents de scie, oscillant entre 30 et 70% du tas maximum utilisé. En effet, la JVM augmente régulièrement le pourcentage d&#x27;utilisation du tas jusqu&#x27;à ce que le processus de ramassage des ordures libère à nouveau de la mémoire. Une forte utilisation du tas se produit lorsque le processus de ramassage des ordures n&#x27;arrive pas à suivre. Un indicateur d&#x27;une utilisation élevée du tas est lorsque le ramasse-miettes est incapable de réduire l&#x27;utilisation du tas à environ 30%.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0a17a67cc589a3c1fbf9e918eadc119df7bd7619-858x278.png" style="width: 100%;" /><p>Dans l&#x27;image ci-dessus, vous pouvez voir une dent de scie normale du tas de la JVM.</p><p>Vous verrez également qu&#x27;il existe deux types de ramassage d&#x27;ordures, le jeune et l&#x27;ancien GC.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8df5c24c4894404de4617be7a13683c9027d607d-875x281.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e01eb2537310b052580411153b8eddc187d97687-890x264.png" style="width: 100%;" /><p>Dans une JVM en bonne santé, le ramassage des ordures devrait idéalement répondre aux conditions suivantes :</p><ul><li>Le jeune GC est traité rapidement (dans les 50 ms).</li><li>Le jeune GC n&#x27;est pas exécuté fréquemment (environ 10 secondes).</li><li>L&#x27;ancienne CG est traitée rapidement (en moins d&#x27;une seconde).</li><li>L&#x27;ancienne CG n&#x27;est pas exécutée fréquemment (une fois toutes les 10 minutes ou plus).</li></ul><h3><strong>Comment résoudre le problème d&#x27;une utilisation trop importante de la mémoire du tas ou d&#x27;une performance non optimale de la JVM ?</strong></h3><p>Plusieurs raisons peuvent expliquer l&#x27;augmentation de l&#x27;utilisation de la mémoire du tas :</p><h4><strong>La surexploitation</strong></h4><p>Veuillez consulter le document sur le surdimensionnement <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards#sizing-shard-guidelines">ici.</a></p><h4><strong>Grandes tailles d&#x27;agrégation</strong></h4><p>Afin d&#x27;éviter des agrégations trop importantes, limitez au maximum le nombre d&#x27;agrégats (taille) dans vos requêtes.</p><pre><code>GET /_search
{
   "aggs" : {
       "products" : {
           "terms" : {
               "field" : "product",
               "size" : 5
                          }
       }
   }
}</code></pre><p>Vous pouvez utiliser la journalisation lente des requêtes (slow logs) et la mettre en œuvre sur un index spécifique en procédant comme suit.</p><pre><code>PUT /my_index/_settings
{
   "index.search.slowlog.threshold.query.warn": "10s",
   "index.search.slowlog.threshold.query.info": "5s",
   "index.search.slowlog.threshold.query.debug": "2s",
   "index.search.slowlog.threshold.query.trace": "500ms",
   "index.search.slowlog.threshold.fetch.warn": "1s",
   "index.search.slowlog.threshold.fetch.info": "800ms",
   "index.search.slowlog.threshold.fetch.debug": "500ms",
   "index.search.slowlog.threshold.fetch.trace": "200ms",
   "index.search.slowlog.level": "info"
}</code></pre><p>Les requêtes qui prennent beaucoup de temps pour donner des résultats sont probablement celles qui consomment beaucoup de ressources.</p><h4><strong>Taille excessive de l&#x27;index en vrac</strong></h4><p>Si vous envoyez des requêtes volumineuses, cela peut être la cause d&#x27;une consommation élevée de la mémoire vive. Essayez de réduire la taille des demandes d&#x27;index en vrac.</p><h4><strong>Questions de cartographie</strong></h4><p>En particulier, si vous utilisez &quot;fielddata : true&quot;, cela peut être un utilisateur majeur de la mémoire vive de votre JVM.</p><h4><strong>La taille du tas n&#x27;est pas correctement définie</strong></h4><p>La taille du tas peut être définie manuellement par :</p><p>Définition de la variable d&#x27;environnement :</p><pre><code>ES_JAVA_OPTS="-Xms2g -Xmx2g"</code></pre><p>Édition du fichier jvm.options dans le répertoire de configuration d&#x27;Elasticsearch :</p><pre><code>-Xms2g
-Xmx2g</code></pre><p>Le paramètre de la variable d&#x27;environnement est prioritaire sur le paramètre du fichier.</p><p>Il est nécessaire de redémarrer le nœud pour que le réglage soit pris en compte.</p><h4><strong>Le nouveau ratio de la JVM n&#x27;est pas correctement défini</strong></h4><p>Il n&#x27;est généralement PAS nécessaire de définir cette valeur, car Elasticsearch le fait par défaut. Ce paramètre définit le ratio de l&#x27;espace disponible pour les objets de &quot;nouvelle génération&quot; et d&#x27;&quot;ancienne génération&quot; dans la JVM.</p><p>Si vous constatez que les anciens GC deviennent très fréquents, vous pouvez essayer de définir spécifiquement cette valeur dans le fichier jvm.options de votre répertoire de configuration Elasticsearch.</p><pre><code>-XX:NewRatio=3</code></pre><h3><strong>Quelles sont les meilleures pratiques pour gérer l&#x27;utilisation de la taille du tas et la collecte des déchets de la JVM dans un grand cluster Elasticsearch ?</strong></h3><p>Les meilleures pratiques pour gérer l&#x27;utilisation de la taille du tas et le ramassage des ordures de la JVM dans un grand cluster Elasticsearch consistent à s&#x27;assurer que la taille du tas est fixée à un maximum de 50% de la RAM disponible et que les paramètres du ramassage des ordures de la JVM sont optimisés pour le cas d&#x27;utilisation spécifique. Il est important de surveiller la taille du tas et les mesures du ramassage des ordures pour s&#x27;assurer que le cluster fonctionne de manière optimale. Plus précisément, il est important de surveiller la taille du tas de la JVM, le temps de ramassage des ordures et les pauses de ramassage des ordures. En outre, il est important de surveiller le nombre de cycles de ramassage des ordures et le temps passé à les effectuer. En surveillant ces mesures, il est possible d&#x27;identifier tout problème potentiel lié à la taille du tas ou aux paramètres du ramassage des ordures et de prendre des mesures correctives si nécessaire.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment augmenter le nombre de shards primaires dans Elasticsearch ?]]></title>
            <description><![CDATA[Apprenez à augmenter le nombre de partitions primaires dans Elasticsearch à l’aide des API split et reindex pour un dimensionnement optimal des partitions.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-increase-primary-shard-count</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-increase-primary-shard-count</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7034b76019a0cba52c25eda29fceb18afc96ed0b-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Il n&#x27;est pas possible d&#x27;augmenter le nombre de shards primaires d&#x27;un index existant, ce qui signifie qu&#x27;un index doit être recréé si vous souhaitez augmenter le nombre de shards primaires. Deux méthodes sont généralement utilisées dans ces situations : l&#x27;API _reindex et l&#x27;API _split.</p><p>L&#x27;API _split est souvent plus rapide que l&#x27;API _reindex. L<strong>&#x27;indexation</strong> <strong>doit être arrêtée</strong> avant les deux opérations, sinon les comptes des documents source_index et cible_index seront différents.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aa0ae010c2f5691ca00440fb453ed6b47bacd24f-1200x628.png" style="width: 100%;" /><h2>Méthode 1 - utilisation de l&#x27;API fractionnée</h2><p>L&#x27;API de fractionnement est utilisée pour créer un nouvel index avec le nombre souhaité d&#x27;unités primaires en copiant les paramètres et en mappant un index existant. Le nombre désiré de fragments primaires peut être défini lors de la création. Les paramètres suivants doivent être vérifiés avant de mettre en œuvre l&#x27;API fractionnée :</p><ol><li>L&#x27;index source doit être en lecture seule. Cela signifie que le processus d&#x27;indexation doit être arrêté.</li><li>Le nombre de groupes primaires dans l&#x27;index cible doit être un multiple du nombre de groupes primaires dans l&#x27;index source. Par exemple, si l&#x27;index source dispose de 5 groupes primaires, les groupes primaires de l&#x27;index cible peuvent être définis comme suit : 10, 15, 20, et ainsi de suite.</li></ol><p>Remarque : si seul le numéro du fonds primaire doit être modifié, il est préférable d&#x27;utiliser l&#x27;API de fractionnement, qui est beaucoup plus rapide que l&#x27;API de réindexation.</p><h3>Mise en œuvre de l&#x27;API fractionnée</h3><p>Créer un index de test :</p><pre><code>POST test_split_source/_doc
{
  "test": "test"
}</code></pre><p>L&#x27;index source doit être en lecture seule pour pouvoir être scindé :</p><pre><code>PUT test_split_source/_settings
{
  "index.blocks.write": true
}</code></pre><p>Les paramètres et les correspondances seront copiés automatiquement à partir de l&#x27;index source :</p><pre><code>POST /test_split_source/_split/test_split_target
{
  "settings": {
    "index.number_of_shards": 3
  }
}</code></pre><p>Vous pouvez vérifier l&#x27;état d&#x27;avancement avec :</p><pre><code>GET _cat/recovery/test_split_target?v&h=index,shard,time,stage,files_percent,files_total</code></pre><p>Étant donné que les paramètres et les mappages sont copiés à partir des index source, l&#x27;index cible est en lecture seule. Activons l&#x27;opération d&#x27;écriture pour l&#x27;index cible :</p><pre><code>PUT test_split_target/_settings
{
    "index.blocks.write": null
}</code></pre><p>Vérifier les index source et cible docs.count avant de supprimer l&#x27;index original :</p><pre><code>GET _cat/indices/test_split*?v&h=index,pri,rep,docs.count</code></pre><p>Le nom de l&#x27;index et le nom de l&#x27;alias ne peuvent pas être identiques. Vous devez supprimer l&#x27;index source et ajouter le nom de l&#x27;index source comme alias à l&#x27;index cible :</p><pre><code>DELETE test_split_source
PUT /test_split_target/_alias/test_split_source</code></pre><p>Après avoir ajouté l&#x27;alias <strong>test_split_source</strong> à l&#x27;index <strong>test_split_target</strong>, vous devez le tester avec :</p><pre><code>GET test_split_source
POST test_split_source/_doc
{
  "test": "test"
}</code></pre><h2>Méthode 2 - utilisation de l&#x27;API de réindexation</h2><p>En créant un nouvel index à l&#x27;aide de l&#x27;API Reindex, il est possible d&#x27;indiquer n&#x27;importe quel nombre de comptes de tessons primaires. Après la création d&#x27;un nouvel index avec le nombre prévu de groupes primaires, toutes les données de l&#x27;index source peuvent être réindexées sur ce nouvel index.</p><p>Outre les fonctionnalités de l&#x27;API fractionnée, les données peuvent être manipulées à l&#x27;aide de la ligne de conduite ingest_pipeline dans l&#x27;API de réindexation. Avec le pipeline d&#x27;ingestion, seuls les champs spécifiés qui correspondent au filtre seront indexés dans l&#x27;index cible à l&#x27;aide de la requête. Le contenu des données peut être modifié à l&#x27;aide d&#x27;un script simple, et plusieurs index peuvent être fusionnés en un seul.</p><h3>Mise en œuvre de l&#x27;API de réindexation</h3><p>Créer un test de réindexation :</p><pre><code>POST test_reindex_source/_doc
{
    "test": "test"
}</code></pre><p>Copier les paramètres et les mappages de l&#x27;index source :</p><pre><code>GET test_reindex_source</code></pre><p>Créez un index cible avec des paramètres, des mappings et le nombre de tiroirs souhaité :</p><pre><code>PUT test_reindex_target
{
  "mappings" : {},
  "settings": {
    "number_of_shards": 10,
    "number_of_replicas": 0,
    "refresh_interval": -1
  }
}</code></pre><p>*Remarque : les paramètres number_of_replicas : 0 et refresh_interval : -1 augmentera la vitesse de réindexation.</p><p>Lancer le processus de réindexation. Les paramètres requests_per_second=-1 et slices=auto permettent d&#x27;ajuster la vitesse de réindexation.</p><pre><code>POST _reindex?requests_per_second=-1&slices=auto&wait_for_completion=false
{
  "source": {
    "index": "test_reindex_source"
  },
  "dest": {
    "index": "test_reindex_target"
  }
}</code></pre><p>Vous verrez l&#x27;identifiant de la tâche lorsque vous exécuterez l&#x27;API de réindexation. Copiez-le et vérifiez avec l&#x27;API _tasks :</p><pre><code>GET _tasks/<task_id></code></pre><p>Mettre à jour les paramètres une fois la réindexation terminée :</p><pre><code>PUT test_reindex_target/_settings
{
  "number_of_replicas": 1,
  "refresh_interval": "1s"
}</code></pre><p>Vérifiez les index source et cible docs.count avant de supprimer l&#x27;index original, ils doivent être identiques :</p><pre><code>GET _cat/indices/test_reindex_*?v&h=index,pri,rep,docs.count</code></pre><p>Le nom de l&#x27;index et le nom de l&#x27;alias ne peuvent pas être identiques. Supprimer l&#x27;index source et ajouter le nom de l&#x27;index source comme alias à l&#x27;index cible :</p><pre><code>DELETE test_reindex_source
PUT /test_reindex_target/_alias/test_reindex_source</code></pre><p>Après avoir ajouté l&#x27;alias test_split_source à l&#x27;index test_split_target, testez-le en utilisant :</p><pre><code>GET test_reindex_source</code></pre><h2>Résumé</h2><p>Si vous souhaitez augmenter le nombre de tessons primaires d&#x27;un index existant, vous devez recréer les paramètres et les correspondances avec un nouvel index. Il existe deux méthodes principales pour ce faire : l&#x27;API de réindexation et l&#x27;API de scission. L&#x27;indexation active doit être arrêtée avant d&#x27;utiliser l&#x27;une ou l&#x27;autre méthode.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-bbq-vs-opensearch-faiss</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-bbq-vs-opensearch-faiss</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
            <pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/796de4829e29566f1f3efa2482f5c3e54b31b1d6-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Recherche vectorielle avec quantification binaire : Elasticsearch avec BBQ est 5 fois plus rapide qu&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7f4f8d08f7bee188423e4e65f0caefc7e34f0355-1600x681.png" style="width: 100%;" /><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&#x27;empreinte mémoire. La recherche s&#x27;effectue alors dans l&#x27;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&#x27;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&#x27;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&#x27;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&#x27;objectif principal était d&#x27;évaluer la performance des deux solutions avec le même niveau de rappel. Que signifie le terme <em>&quot;rappel&quot;</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&#x27;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&#x27;appliquer le classement final. Cela signifie que pour Rappel@10, Rappel@50 et Rappel@100, le système récupère d&#x27;abord <em>n</em> candidats à l&#x27;aide de l&#x27;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&#x27;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&#x27;ensemble initial.</p><p>Dans cette comparaison, Elasticsearch a démontré une latence plus faible et un débit plus élevé qu&#x27;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>1 pool de nœuds pour Elasticsearch 9.0 avec 3 machines <code>e2-standard-32</code> (128GB RAM et 32 CPUs)</li><li>1 pool de nœuds pour OpenSearch 2.19 avec 3 machines <code>e2-standard-32</code> (128GB RAM et 32 CPUs)</li><li>1 pool de nœuds pour Rallye avec 2 machines <code>e2-standard-4</code> (16GB RAM et 4 CPUs)</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/169bb6033f6eebfd1b177b3446bf916fde4ee5c5-1600x856.png" style="width: 100%;" /><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&#x27;<a href="https://huggingface.co/datasets/BeIR/nq">ensemble de données NQ</a> enrichis avec des embeddings générés à l&#x27;aide du <a href="https://openai.com/blog/new-and-improved-embedding-model">modèle text-embedding-ada-002</a> d&#x27;OpenAI.</p><pre><code>{
  "source-file": "open_ai_corpus-initial-indexing.json.bz2",
  "document-count": 2580961,
  "compressed-bytes": 32076749416,
  "uncompressed-bytes": 90263571686
}</code></pre><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 &quot;facteur de suréchantillon&quot; de 1). Chaque recherche a été exécutée 10 000 fois avec 1000 recherches comme échauffement :</p><p></p><p><strong><span style="text-decoration:underline">Rappel@10</span></strong></p><ul><li>10-40-40</li><li>10-50-50</li><li>10-100-100</li><li>10-200-200</li><li>10-500-500</li><li>10-750-750</li><li>10-1000-1000</li><li>10-1500-1500</li><li>10-2000-2000</li></ul><p><strong><span style="text-decoration:underline">Rappel@50</span></strong></p><ul><li>50-150-150</li><li>50-200-200</li><li>50-250-250</li><li>50-500-500</li><li>50-750-750</li><li>50-1000-1000</li><li>50-1200-1200</li><li>50-1500-1500</li><li>50-2000-2000</li></ul><p><strong><span style="text-decoration:underline">Rappel@100</span></strong></p><ul><li>100-200-200</li><li>100-250-250</li><li>100-300-300</li><li>100-500-500</li><li>100-750-750</li><li>100-1000-1000</li><li>100-1200-1200</li><li>100-1500-1500</li><li>100-2000-2000</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&#x27;importe quelle combinaison de k, n et rescore.</p><h3>Configuration d&#x27;OpenSearch Rally</h3><p><code>/k8s/rally-openai_vector-os-bq.yml</code></p><pre><code>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]
      ]
    }</code></pre><h3>Configuration de l&#x27;index Opensearch</h3><p>Les variables du ConfigMap sont ensuite utilisées pour la configuration de l&#x27;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 &quot;32x&quot;.</a></p><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p><pre><code>{
  "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
          }
        }
      }
    }
  }
}</code></pre><h3>Configuration d&#x27;Elasticsearch Rally</h3><p><code>/k8s/rally-openai_vector-es-bq.yml</code></p><pre><code>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]
      ]
    }</code></pre><h3>Configuration de l&#x27;index Elasticsearch</h3><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p><pre><code>{
  "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
        }
      }
    }
  }
}</code></pre><h2>Résultats</h2><p>Il y a plusieurs façons d&#x27;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&#x27;on considère que &quot;plus c&#x27;est élevé, mieux c&#x27;est&quot; pour chaque indicateur. Cependant, le temps de latence est un facteur négatif (plus il est faible, mieux c&#x27;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 &quot;vitesse&quot;) et<strong> rappel * débit</strong>, de sorte que les deux mesures signifient qu&#x27;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&#x27;à <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&#x27;OpenSearch FAISS.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d580ad53e8974bd3aa75957c413a0136c4e465c5-1600x681.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/18edce667fe36ab95033264ef8df6f352dda2425-2044x866.png" style="width: 100%;" /><h4>Rappel @ 10 - Détaillé</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a7459b87e679f4ad963d0e2f1685499b40f6f050-1600x799.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c6036f55a13377654d296eb3148c7199e1965475-1600x799.png" style="width: 100%;" />
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">tâche</td><td style="border: 1px solid #ccc; padding: 4px;">latence.moyenne</td><td style="border: 1px solid #ccc; padding: 4px;">débit.moyen</td><td style="border: 1px solid #ccc; padding: 4px;">avg_recall</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-100-100</td><td style="border: 1px solid #ccc; padding: 4px;">11.70</td><td style="border: 1px solid #ccc; padding: 4px;">513.58</td><td style="border: 1px solid #ccc; padding: 4px;">0.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-1000-100</td><td style="border: 1px solid #ccc; padding: 4px;">27.33</td><td style="border: 1px solid #ccc; padding: 4px;">250.55</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">35.93</td><td style="border: 1px solid #ccc; padding: 4px;">197.26</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">13.33</td><td style="border: 1px solid #ccc; padding: 4px;">456.16</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">44.27</td><td style="border: 1px solid #ccc; padding: 4px;">161.40</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-40-40</td><td style="border: 1px solid #ccc; padding: 4px;">10.97</td><td style="border: 1px solid #ccc; padding: 4px;">539.94</td><td style="border: 1px solid #ccc; padding: 4px;">0.84</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-50-50</td><td style="border: 1px solid #ccc; padding: 4px;">11.00</td><td style="border: 1px solid #ccc; padding: 4px;">535.73</td><td style="border: 1px solid #ccc; padding: 4px;">0.85</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">19.52</td><td style="border: 1px solid #ccc; padding: 4px;">341.45</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">22.94</td><td style="border: 1px solid #ccc; padding: 4px;">295.19</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-100-100</td><td style="border: 1px solid #ccc; padding: 4px;">35.59</td><td style="border: 1px solid #ccc; padding: 4px;">200.61</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">156.81</td><td style="border: 1px solid #ccc; padding: 4px;">58.30</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">181.79</td><td style="border: 1px solid #ccc; padding: 4px;">42.97</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">47.91</td><td style="border: 1px solid #ccc; padding: 4px;">155.16</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">232.14</td><td style="border: 1px solid #ccc; padding: 4px;">31.84</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-40-40</td><td style="border: 1px solid #ccc; padding: 4px;">27.55</td><td style="border: 1px solid #ccc; padding: 4px;">249.25</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-50-50</td><td style="border: 1px solid #ccc; padding: 4px;">28.78</td><td style="border: 1px solid #ccc; padding: 4px;">245.14</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">79.44</td><td style="border: 1px solid #ccc; padding: 4px;">97.06</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">104.19</td><td style="border: 1px solid #ccc; padding: 4px;">75.49</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
              </tbody>
            </table>
          <h3>Rappel à 50 ans - simplifié</h3><p>À ce niveau de rappel, Elasticsearch BBQ est jusqu <strong>&#x27;à 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 moyenne<strong> </strong>qu&#x27;OpenSearch FAISS.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3189ffb330948b35854eeea9ae317d4846c14972-1600x681.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/607383d674dcf0b8f94bfb1a450063f52fcbeb15-2060x876.png" style="width: 100%;" /><h4>Résultats détaillés - Rappel à 50</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ebfffe0b776aad31dd03d315cfbf5aa098b41226-1600x789.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ae20cf870a65c400a2112bbad62eb56e244f549a-1600x799.png" style="width: 100%;" />
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Tâche</td><td style="border: 1px solid #ccc; padding: 4px;">Latence Moyenne</td><td style="border: 1px solid #ccc; padding: 4px;">Débit moyen</td><td style="border: 1px solid #ccc; padding: 4px;">Rappel moyen</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">25.71</td><td style="border: 1px solid #ccc; padding: 4px;">246.44</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">28.81</td><td style="border: 1px solid #ccc; padding: 4px;">227.85</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-150-150</td><td style="border: 1px solid #ccc; padding: 4px;">13.43</td><td style="border: 1px solid #ccc; padding: 4px;">362.90</td><td style="border: 1px solid #ccc; padding: 4px;">0.90</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">33.38</td><td style="border: 1px solid #ccc; padding: 4px;">202.37</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">12.99</td><td style="border: 1px solid #ccc; padding: 4px;">406.30</td><td style="border: 1px solid #ccc; padding: 4px;">0.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">42.63</td><td style="border: 1px solid #ccc; padding: 4px;">163.68</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">14.41</td><td style="border: 1px solid #ccc; padding: 4px;">373.21</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">17.15</td><td style="border: 1px solid #ccc; padding: 4px;">341.04</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">31.25</td><td style="border: 1px solid #ccc; padding: 4px;">248.60</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">125.35</td><td style="border: 1px solid #ccc; padding: 4px;">62.53</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">143.87</td><td style="border: 1px solid #ccc; padding: 4px;">54.75</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-150-150</td><td style="border: 1px solid #ccc; padding: 4px;">43.64</td><td style="border: 1px solid #ccc; padding: 4px;">130.01</td><td style="border: 1px solid #ccc; padding: 4px;">0.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">169.45</td><td style="border: 1px solid #ccc; padding: 4px;">46.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">48.05</td><td style="border: 1px solid #ccc; padding: 4px;">156.07</td><td style="border: 1px solid #ccc; padding: 4px;">0.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">216.73</td><td style="border: 1px solid #ccc; padding: 4px;">36.38</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">53.52</td><td style="border: 1px solid #ccc; padding: 4px;">142.44</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">78.98</td><td style="border: 1px solid #ccc; padding: 4px;">97.82</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">103.20</td><td style="border: 1px solid #ccc; padding: 4px;">75.86</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
              </tbody>
            </table>
          <h3>Rappel à 100</h3><p>À ce niveau de rappel, Elasticsearch BBQ est jusqu <strong>&#x27;à 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&#x27;OpenSearch FAISS.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d3f29db64cbde9956de1fa3ae64a75f15141a2bb-1600x681.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/10aff7f8c61fdac895b9ba9c5342baf239ba3ffc-2072x864.png" style="width: 100%;" /><h4>Résultats détaillés - Rappel à 100</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8790fdf9512b850447f6875fb69969f6f1d4da5f-1600x799.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d47032fac6c288cd4eedd9f25001e417b2fa9d65-1600x787.png" style="width: 100%;" />
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">tâche</td><td style="border: 1px solid #ccc; padding: 4px;">latence.moyenne</td><td style="border: 1px solid #ccc; padding: 4px;">débit.moyen</td><td style="border: 1px solid #ccc; padding: 4px;">avg_recall</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">27.82</td><td style="border: 1px solid #ccc; padding: 4px;">243.22</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">31.14</td><td style="border: 1px solid #ccc; padding: 4px;">224.04</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">35.98</td><td style="border: 1px solid #ccc; padding: 4px;">193.99</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">14.18</td><td style="border: 1px solid #ccc; padding: 4px;">403.86</td><td style="border: 1px solid #ccc; padding: 4px;">0.88</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">45.36</td><td style="border: 1px solid #ccc; padding: 4px;">159.88</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">14.77</td><td style="border: 1px solid #ccc; padding: 4px;">433.06</td><td style="border: 1px solid #ccc; padding: 4px;">0.90</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-300-300</td><td style="border: 1px solid #ccc; padding: 4px;">14.61</td><td style="border: 1px solid #ccc; padding: 4px;">375.54</td><td style="border: 1px solid #ccc; padding: 4px;">0.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">18.88</td><td style="border: 1px solid #ccc; padding: 4px;">340.37</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">23.59</td><td style="border: 1px solid #ccc; padding: 4px;">285.79</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">142.90</td><td style="border: 1px solid #ccc; padding: 4px;">58.48</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">153.03</td><td style="border: 1px solid #ccc; padding: 4px;">51.04</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">181.79</td><td style="border: 1px solid #ccc; padding: 4px;">43.20</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">50.94</td><td style="border: 1px solid #ccc; padding: 4px;">131.62</td><td style="border: 1px solid #ccc; padding: 4px;">0.83</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">232.53</td><td style="border: 1px solid #ccc; padding: 4px;">33.67</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">57.08</td><td style="border: 1px solid #ccc; padding: 4px;">131.23</td><td style="border: 1px solid #ccc; padding: 4px;">0.87</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-300-300</td><td style="border: 1px solid #ccc; padding: 4px;">62.76</td><td style="border: 1px solid #ccc; padding: 4px;">120.10</td><td style="border: 1px solid #ccc; padding: 4px;">0.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">84.36</td><td style="border: 1px solid #ccc; padding: 4px;">91.54</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">111.33</td><td style="border: 1px solid #ccc; padding: 4px;">69.95</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
              </tbody>
            </table>
          <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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/45a0acfe5985bff28ccada76ec4eca190fe65f72-1600x799.png" style="width: 100%;" /><p>Dans Elasticsearch 8.18 et 9.0, nous avons réécrit l&#x27;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&#x27;une plus grande précision dans la recherche vectorielle sans compromettre les performances, ce qui rend la recherche vectorielle d&#x27;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&#x27;à 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><strong>Recall@10</strong>: Elasticsearch BBQ est jusqu&#x27;à 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.</li><li><strong>Recall@50</strong>: Elasticsearch BBQ est jusqu&#x27;à 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.</li><li><strong>Recall@100</strong>: Elasticsearch BBQ est jusqu&#x27;à 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.</li></ul><p>Ces résultats mettent en évidence les avantages d&#x27;Elasticsearch BBQ en termes d&#x27;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&#x27;utilisation de recherche et d&#x27;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&#x27;espace qu&#x27;auparavant, en s&#x27;appuyant sur les gains de Lucene 10. Ce blog est une autre illustration de cette innovation.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment migrer des données entre différentes versions d'Elasticsearch & entre clusters]]></title>
            <description><![CDATA[Exploration des méthodes de transfert de données entre les versions et les clusters d'Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-migrate-data-versions-clusters</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-migrate-data-versions-clusters</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Mon, 14 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/01fde3b1d714f12bf8673140c9f2f940d443de31-1440x823.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Lorsque vous souhaitez mettre à niveau un cluster Elasticsearch, il est parfois plus facile de créer un nouveau cluster séparé et de transférer les données de l&#x27;ancien cluster vers le nouveau. Les utilisateurs ont ainsi l&#x27;avantage de pouvoir tester toutes leurs données et configurations sur le nouveau cluster avec toutes leurs applications sans risque d&#x27;interruption ou de perte de données.</p><p>Les inconvénients de cette approche sont qu&#x27;elle nécessite une certaine duplication du matériel et qu&#x27;elle peut créer des difficultés lors du transfert et de la synchronisation de toutes les données.</p><p>Il peut également être nécessaire d&#x27;effectuer une procédure similaire si vous devez migrer des applications d&#x27;un centre de données à un autre.</p><p>Dans cet article, nous allons discuter et détailler trois façons de transférer des données entre les clusters Elasticsearch.</p><p><strong>Comment migrer des données entre clusters Elasticsearch ?</strong></p><p>Il existe trois façons de transférer des données entre les clusters Elasticsearch :</p><ol><li><a href="https://www.elastic.co/fr/search-labs/blog/elasticsearch-migrate-data-versions-clusters#1.-reindexing-data-from-a-remote-cluster">Réindexation à partir d&#x27;un cluster distant</a></li><li><a href="https://www.elastic.co/fr/search-labs/blog/elasticsearch-migrate-data-versions-clusters#2.-transferring-data-using-snapshots">Transfert de données à l&#x27;aide d&#x27;instantanés</a></li><li><a href="https://www.elastic.co/fr/search-labs/blog/elasticsearch-migrate-data-versions-clusters#3.-transferring-data-using-logstash">Transférer des données avec Logstash</a></li></ol><p>L&#x27;utilisation d&#x27;instantanés est généralement le moyen le plus rapide et le plus fiable de transférer des données. Toutefois, n&#x27;oubliez pas que vous ne pouvez restaurer un instantané que sur un cluster de version égale ou supérieure et jamais avec une différence de plus d&#x27;une version majeure. Cela signifie que vous pouvez restaurer un snapshot 6.x sur un cluster 7.x mais pas sur un cluster 8.x.</p><p>Si vous avez besoin d&#x27;augmenter de plus d&#x27;une version majeure, vous devrez réindexer ou utiliser Logstash.</p><p>Examinons maintenant en détail chacune des trois options de transfert de données entre clusters Elasticsearch.</p><h2>1. Réindexation des données d&#x27;un cluster distant</h2><p>Avant de commencer à réindexer, n&#x27;oubliez pas que vous devrez configurer les mappages appropriés pour tous les index sur le nouveau cluster. Pour ce faire, vous devez soit créer les index directement avec les mappings appropriés, soit utiliser des modèles d&#x27;index.</p><h3>Réindexation à distance - configuration requise</h3><p>Pour réindexer à distance, vous devez ajouter la configuration ci-dessous au fichier elasticseearch.yml pour le cluster qui reçoit les données, qui, dans les systèmes Linux, est généralement situé ici : /etc/elasticsearch/elasticsearch.yml. La configuration à ajouter est la suivante :</p><pre><code>reindex.remote.whitelist: "192.168.1.11:9200"</code></pre><p>Si vous utilisez SSL, vous devez ajouter le certificat CA à chaque nœud et inclure ce qui suit dans la commande pour chaque nœud dans elasticsearch.yml :</p><pre><code>reindex.ssl.certificate_authorities: “/path/to/ca.pem”</code></pre><p>Vous pouvez également ajouter la ligne ci-dessous à tous les nœuds Elasticsearch afin de désactiver la vérification SSL. Toutefois, cette approche est moins recommandée car elle n&#x27;est pas aussi sûre que l&#x27;option précédente :</p><pre><code>reindex.remote.whitelist: "192.168.1.11:9200"
reindex.ssl.verification_mode: none
systemctl restart elasticsearch service </code></pre><p>Vous devrez effectuer ces modifications sur chaque nœud et procéder à un redémarrage progressif. Pour plus d&#x27;informations sur la manière de procéder, veuillez consulter <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.17/restart-cluster.html#restart-cluster-rolling">notre guide.</a></p><h3>Commande de réindexation</h3><p>Après avoir défini l&#x27;hôte distant dans le fichier elasticsearch.yml et ajouté les certificats SSL si nécessaire, vous pouvez commencer à réindexer les données avec la commande ci-dessous :</p><pre><code>POST _reindex
{
  "source": {
    "remote": {
      "host": "http://192.168.1.11:9200",
      "username": "elastic",
      "password": "123456",
     "socket_timeout": "1m",
      "connect_timeout": "1m"

    },
    "index": "companydatabase"
  },
  "dest": {
    "index": "my-new-index-000001"
  }
}</code></pre><p>Il peut donc être utile de fixer des valeurs généreuses pour les délais d&#x27;attente plutôt que de se fier aux valeurs par défaut.</p><p>Examinons maintenant d&#x27;autres erreurs courantes que vous pouvez rencontrer lors d&#x27;une réindexation à distance.</p><h3>Erreurs courantes lors de la réindexation à distance</h3><h4>1. La réindexation ne figure pas sur la liste blanche</h4><pre><code>{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "[192.168.1.11:9200] not whitelisted in reindex.remote.whitelist"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "[192.168.1.11:9200] not whitelisted in reindex.remote.whitelist"
  },
  "status": 400
}</code></pre><p>Si vous rencontrez cette erreur, cela signifie que vous n&#x27;avez pas défini l&#x27;adresse IP de l&#x27;hôte distant ou le nom du nœud DNS dans Elasticsearch comme décrit ci-dessus ou que vous avez oublié de redémarrer les services Elasticsearch.</p><p>Pour résoudre ce problème dans le cluster Elasticsearch, vous devez ajouter l&#x27;hôte distant à tous les nœuds Elasticsearch et redémarrer les services Elasticsearch.</p><h4>2. Exception relative au handshake SSL</h4><pre><code>{
  "error": {
    "root_cause": [
      {
        "type": "s_s_l_handshake_exception",
        "reason": "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"
      }
    ],
    "type": "s_s_l_handshake_exception",
    "reason": "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target",
    "caused_by": {
      "type": "validator_exception",
      "reason": "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target",
      "caused_by": {
        "type": "sun_cert_path_builder_exception",
        "reason": "unable to find valid certification path to requested target"
      }
    }
  },
  "status": 500
}</code></pre><p>Cette erreur signifie que vous avez oublié d&#x27;ajouter le certificat reindex.ssl.certificate_authorities à elasticsearch.yml comme décrit ci-dessus. Pour l&#x27;ajouter :</p><pre><code>#elasticsearch.yml
reindex.ssl.certificate_authorities: "/path/to/ca.pem"</code></pre><h2>2. Transfert de données à l&#x27;aide d&#x27;instantanés</h2><p>N&#x27;oubliez pas, comme indiqué ci-dessus, que vous ne pouvez restaurer un instantané que sur un cluster de version égale ou supérieure et jamais avec une différence de plus d&#x27;une version majeure.</p><p>Si vous avez besoin d&#x27;augmenter de plus d&#x27;une version majeure, vous devrez réindexer ou utiliser Logstash.</p><p>Les étapes suivantes sont nécessaires pour transférer des données par le biais d&#x27;instantanés :</p><p>Étape 1. Ajouter le plugin de référentiel au premier cluster Elasticsearch - Afin de transférer des données entre clusters via des snapshots, vous devez vous assurer que le référentiel est accessible à la fois depuis le nouveau et l&#x27;ancien cluster. Les référentiels de stockage en nuage tels que AWS, Google et Azure sont généralement idéaux pour cela. Pour prendre des instantanés, veuillez consulter <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/snapshot-restore.html">notre guide</a> et suivre les étapes qu&#x27;il décrit.</p><p>Étape 2. Redémarrer le service Elasticsearch (rolling restart).</p><p>Étape 3. Créez un référentiel pour le premier cluster Elasticsearch.</p><p>Étape 4 - Ajouter le plugin de référentiel au deuxième cluster Elasticsearch.</p><p>Étape 5- Ajouter un référentiel en lecture seule au deuxième cluster Elasticsearch - Vous devrez ajouter un référentiel en répétant les mêmes étapes que celles que vous avez suivies pour créer le premier cluster Elasticsearch.</p><p>Remarque importante : lorsque vous connectez le deuxième cluster Elasticsearch au même référentiel AWS S3, vous devez définir le référentiel comme un référentiel en lecture seule :</p><pre><code>PUT _snapshot/my_s3_repository
{
  "type": "s3",
  "settings": {
    "bucket": "my-analytic-data",
    "endpoint": "s3.eu-de.cloud-object-storage.appdomain.cloud",
    "readonly": "true"
  }
}</code></pre><p>C&#x27;est important parce que vous voulez éviter le risque de mélanger les versions d&#x27;Elasticsearch dans le même dépôt d&#x27;instantanés.</p><p>Étape 6 - Restauration des données vers le deuxième cluster Elasticsearch - Après avoir suivi les étapes ci-dessus, vous pouvez restaurer les données et les transférer vers le nouveau cluster. Veuillez suivre les étapes décrites dans <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/snapshot-restore.html">cet article</a> pour restaurer les données dans le nouveau cluster. </p><h2>3. Transfert de données à l&#x27;aide de Logstash</h2><p>Avant de commencer à transférer les données avec logstash, n&#x27;oubliez pas que vous devrez configurer les mappings appropriés pour tous les index sur le nouveau cluster. Pour ce faire, vous devrez soit créer les index directement, soit utiliser des modèles d&#x27;index.</p><p>Pour transférer des données entre deux clusters Elasticsearch, vous pouvez configurer un serveur Logstash temporaire et l&#x27;utiliser pour transférer vos données entre les deux clusters. Pour les petits clusters, une instance de 2 Go de mémoire vive devrait suffire. Pour les clusters plus importants, vous pouvez utiliser des CPU à quatre cœurs avec 8 Go de RAM.</p><p>Pour des conseils sur l&#x27;installation de Logstash, <a href="https://www.elastic.co/fr/guide/en/logstash/current/installing-logstash.html">voir ici.</a></p><h3>Configuration de Logstash pour le transfert de données d&#x27;un cluster à l&#x27;autre</h3><p>La configuration de base pour copier un index unique du cluster A vers le cluster B est la suivante :</p><pre><code>iinput
{
elasticsearch
      {
        hosts => ["192.168.1.11:9200"]
        index => "index_name"
       docinfo => true      
      }
}

output 
{
  elasticsearch {
        hosts => "https://192.168.1.12:9200"
        index => "index_name"
        
  }
}</code></pre><p>Pour sécuriser elasticsearch, vous pouvez utiliser la configuration ci-dessous :</p><pre><code>input
{
  elasticsearch
      {
        hosts => ["192.168.1.11:9200"]
        index => "index_name"
        docinfo => true 
        user => "elastic"
        password => "elastic_password"
        ssl => true
        ssl_certificate_verification => false
            
      }
}

output 
{
  elasticsearch {
        hosts => "https://192.168.1.12:9200"
        index => "index_name"
        user => "elastic"
        password => "elastic_password"
        ssl => true
        ssl_certificate_verification => false
  }
}</code></pre><h3>Métadonnées de l&#x27;index</h3><p>Les commandes ci-dessus écrivent dans un seul index nommé. Si vous souhaitez transférer plusieurs index et préserver les noms d&#x27;index, vous devez ajouter la ligne suivante à la sortie de Logstash :</p><pre><code>index => "%{[@metadata][_index]}"</code></pre><p>De même, si vous souhaitez conserver l&#x27;identifiant original du document, vous devrez ajouter :</p><pre><code>document_id => "%{[@metadata][_id]}"</code></pre><p>Gardez à l&#x27;esprit que la définition de l&#x27;ID du document ralentira considérablement le transfert des données, aussi ne conservez-vous l&#x27;ID d&#x27;origine que si vous en avez besoin.</p><h2>Synchronisation des mises à jour</h2><p>Toutes les méthodes décrites ci-dessus prennent un temps relativement long, et il se peut que des données aient été mises à jour dans le cluster d&#x27;origine en attendant la fin du processus.</p><p>Il existe plusieurs stratégies pour permettre la synchronisation des mises à jour qui ont pu avoir lieu pendant le processus de transfert des données, et vous devriez réfléchir à ces questions avant d&#x27;entamer ce processus. Vous devez notamment réfléchir aux points suivants :</p><ul><li>Quelle méthode utilisez-vous pour identifier les données qui ont été mises à jour/ajoutées depuis le début du processus de transfert de données (par exemple, un champ &quot;last_update_time&quot; dans les données) ?</li><li>Quelle méthode pouvez-vous utiliser pour transférer le dernier élément de données ?</li><li>Existe-t-il un risque de duplication des documents ? En général, c&#x27;est le cas, à moins que la méthode que vous utilisez ne fixe l&#x27;ID du document à une valeur connue lors de la réindexation).</li></ul><p>Les différentes méthodes permettant la synchronisation des mises à jour sont décrites ci-dessous.</p><h3>1. Utilisation des systèmes de file d&#x27;attente</h3><p>Certains systèmes d&#x27;ingestion/mise à jour utilisent des files d&#x27;attente qui vous permettent de &quot;rejouer&quot; les modifications de données reçues au cours des x derniers jours. Cela peut permettre de synchroniser les modifications effectuées. </p><h3>2. Réindexation à distance</h3><p>Répétez le processus de réindexation pour tous les éléments pour lesquels &quot;last_update_time&quot; &gt; a été effectué il y a x jours. Vous pouvez le faire en ajoutant un paramètre &quot;query&quot; à la requête de réindexation.</p><h3>3. Logstash</h3><p>Dans l&#x27;entrée Logstash, vous pouvez ajouter une requête pour filtrer tous les éléments pour lesquels &quot;last_update_time&quot; &gt; x jours ago. Toutefois, ce processus entraînera des doublons dans les données non chronologiques, à moins que vous n&#x27;ayez défini l&#x27;identifiant du document.</p><h3>4. Instantanés</h3><p>Il n&#x27;est pas possible de restaurer une partie seulement d&#x27;un index. Vous devez donc utiliser l&#x27;une des autres méthodes de transfert de données décrites ci-dessus (ou un script) pour mettre à jour toutes les modifications qui ont eu lieu depuis le processus de transfert de données.</p><p>Cependant, la restauration des instantanés est un processus beaucoup plus rapide que la réindexation/Logstash, il est donc possible de suspendre les mises à jour pendant une courte période de temps pendant que les instantanés sont transférés afin d&#x27;éviter le problème.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Valerio Arvizzigno]]></dc:creator>
            <pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d1f1c8860f1f0b989ad698a882f869de7284ab78-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic est ravi d&#x27;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&#x27;informations pris en charge en mode natif, ce qui permet aux utilisateurs d&#x27;exploiter les forces multimodales des modèles Gemini de Google avec les capacités de recherche sémantique et hybride d&#x27;Elasticsearch, alimentées par l&#x27;IA.</p><p>Les développeurs peuvent désormais créer leurs applications RAG dans le cadre d&#x27;un parcours unifié, en fondant leurs expériences de chat sur leurs données privées d&#x27;une manière flexible et à code bas. Que vous construisiez des agents d&#x27;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&#x27;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&#x27;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&#x27;intégration d&#x27;Elasticsearch avec la plateforme Vertex AI de Google Cloud pour un ancrage transparent des données et la création d&#x27;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 &quot;Grounding&quot; qui leur permet d&#x27;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><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) ;</li><li><a href="https://cloud.google.com/generative-ai-app-builder/docs/grounded-gen">Grounded Generation API</a>, utilisée plutôt dans l&#x27;écosystème Vertex AI Agent Builder pour construire des expériences agentiques.</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&#x27;entreprise pertinentes là où elles sont nécessaires dans vos chats internes et en contact avec les clients, ce qui est crucial pour l&#x27;adoption réelle de la GenAI dans les processus d&#x27;entreprise.</p><p>Les API susmentionnées permettront aux développeurs d&#x27;adopter cette nouvelle fonctionnalité dans leur code. Cependant, l&#x27;ingénierie et les tests rapides restent des étapes cruciales dans le développement de l&#x27;application et servent de terrain de jeu initial. Pour ce faire, Elasticsearch est conçu pour être facilement évalué par les utilisateurs dans l&#x27;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&#x27;onglet &quot;Customize Grounding&quot; de l&#x27;interface utilisateur, comme indiqué ci-dessous (notez que pour que cela fonctionne, vous devez saisir la clé API avec le mot &quot;ApiKey&quot; dans l&#x27;interface utilisateur et les exemples de code ci-dessous). Vous êtes maintenant prêt à générer vos propres connaissances !</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/69b3d979d18fa90742d9397f57975c586edf5d9f-1003x710.gif" style="width: 100%;" /><h2>Des applications GenAI prêtes à la production en toute simplicité</h2><p>Elastic et Google Cloud s&#x27;efforcent d&#x27;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&#x27;applications GAI sur Vertex AI, en évitant les API supplémentaires inutiles et l&#x27;orchestration des données tout en s&#x27;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&#x27;API LLM :</p><pre><code>curl -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \https://us-central1-aiplatform.googleapis.com/v1beta1/projects/<PROJECT_ID>/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://<my-elastic-cluster>.gcp.elastic-cloud.com:9243",
    "apiAuth": {
      "apiKeyConfig": {
            "apiKeyString": "ApiKey <API_KEY>"
      }
    },
    "elasticSearchParams": {
      "index": "<my-index>",
      "searchTemplate": "<my-search-template>"
    }
      }
    }
  }]
}</code></pre><p>Dans l&#x27;exemple ci-dessus, avec le champ <code>retrieval</code> de l&#x27;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 &quot;ELASTIC_SEARCH&quot; permet d&#x27;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&#x27;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&#x27;API &quot;Grounding Generation&quot;, en définissant le paramètre <code>groundingSpec</code>:</p><pre><code>curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" https://us-discoveryengine.googleapis.com/v1alpha/projects/<PROJECT_ID>/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://<my-elastic-cluster>.gcp.elastic-cloud.com:9243",
        "index": "<my-index>",
        "searchTemplate": "<my-search-template",
        "apiKey": "projects/<PROJECT_ID>/secrets/api-key/versions/latest"
      }
    }]
  }
}
'</code></pre><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&#x27;utilisation. C&#x27;est dans cet esprit que nous l&#x27;avons conçu pour vous permettre d&#x27;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&#x27;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&#x27;exécution de requêtes similaires avec des paramètres différents, car ils permettent d&#x27;économiser du temps de développement et de réduire le risque d&#x27;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&#x27;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&#x27;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&#x27;applications web ou les ingénieurs en intelligence artificielle, qui n&#x27;ont qu&#x27;à spécifier le nom du modèle dans l&#x27;API de base.</p><p>Cette conception permet une personnalisation complète, mettant à votre disposition les fonctionnalités de recherche étendues d&#x27;Elasticsearch dans un environnement Google Cloud AI, tout en garantissant la modularité, la transparence et la facilité d&#x27;utilisation pour les différents développeurs, même ceux qui ne sont pas familiers avec Elastic.</p><p>Lorsque vous avez besoin d&#x27;une recherche BM25, d&#x27;une recherche sémantique ou d&#x27;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&#x27;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&#x27;applique également aux modèles d&#x27;intégration et de classement que vous choisissez pour gérer les vecteurs et les résultats. En fonction de votre cas d&#x27;utilisation, vous pouvez héberger des modèles sur les nœuds ML d&#x27;Elastic, utiliser un point de terminaison de service tiers via l&#x27;API d&#x27;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&#x27;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>Recherche sémantique avec le modèle ELSER (vecteurs épars et regroupement)</li><li>Recherche sémantique avec le modèle multilingue e5 (vecteurs denses et regroupement)</li><li>Recherche hybride avec le modèle d&#x27;insertion de texte de Vertex AI</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&#x27;embeddings avec les API Vertex AI de Google Cloud sur un catalogue de produits. Tout d&#x27;abord, nous devons créer le modèle de recherche dans Elasticsearch comme indiqué ci-dessous :</p><pre><code>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
          }
          ]
    }  
  }
}</code></pre><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&#x27;images.</p><p>Les vecteurs sont créés à la volée au moment de la requête sur l&#x27;entrée soumise via un point de terminaison d&#x27;inférence de l&#x27;API d&#x27;intégration de Vertex AI, <code>googlevertexai_embeddings_004</code>, précédemment défini comme suit :</p><pre><code>PUT /_inference/text_embedding/googlevertexai_embeddings_004
{
    "service": "googlevertexai",
    "service_settings": {
        "service_account_json": "<your_service_account_key>",
        "model_id": "text-embedding-004",
        "location": "us-central1",
        "project_id": "<your_gcp_project>"
    }
}</code></pre><p>Vous trouverez des informations supplémentaires sur l&#x27;utilisation de l&#x27;API d&#x27;inférence d&#x27;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><pre><code>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
  }
}</code></pre><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&#x27;entrée suivantes :</p><ul><li>&quot;query&quot; - la requête de l&#x27;utilisateur à rechercher</li><li>&quot;index_name&quot; - le nom de l&#x27;index dans lequel la recherche doit être effectuée</li><li>&quot;num_hits&quot; - le nombre de documents que nous voulons retrouver dans la sortie finale</li></ul><p>Voici un exemple de résultat :</p><pre><code>{
        "_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"
        }</code></pre><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 &quot;De quoi ai-je besoin pour réparer mes cloisons sèches ?&quot;, au lieu d&#x27;obtenir une suggestion générique, l&#x27;agent conversationnel vous fournira des produits spécifiques !</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8c8b1374da7e9b2c17df8758acb448eed5b74e2d-1473x913.png" style="width: 100%;" /><h2>Parcours GenAI de bout en bout avec Elastic et Google Cloud</h2><p>Elastic s&#x27;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&#x27;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&#x27;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&#x27;intégration, de reclassement et d&#x27;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&#x27;<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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b1fc48dba1713cd01c4d7f3296587d0c4e6e7e0c-862x651.png" style="width: 100%;" /><p>Mais il ne s&#x27;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&#x27;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&#x27;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&#x27;ingestion de données et la génération d&#x27;embedding jusqu&#x27;à l&#x27;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&#x27;avez pas encore fait, l&#x27;une des façons les plus simples de démarrer avec Elastic Search AI Platform et d&#x27;explorer nos capacités est d&#x27;<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&#x27;Elastic. Toute fonctionnalité ou fonction qui n&#x27;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&#x27;Elasticsearch N.V. aux États-Unis et dans d&#x27;autres pays. Tous les autres noms de produits et d&#x27;entreprises sont des marques commerciales, des logos ou des marques déposées appartenant à leurs propriétaires respectifs.</em></p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/hnsw-graphs-speed-up-merging</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/hnsw-graphs-speed-up-merging</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Mayya Sharipova]]></dc:creator>
            <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/49a6c880f5dedd0fa502ece5be124824ee218cc0-1792x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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&#x27;autres améliorations que nous avions prévues. Ce billet est l&#x27;aboutissement de ce travail.</p><p>Vous vous demandez peut-être pourquoi utiliser des graphiques multiples ? Il s&#x27;agit d&#x27;un effet secondaire d&#x27;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&#x27;index et la possibilité de découpler le calcul de l&#x27;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&#x27;idées, nous pensons que la possibilité de mesurer les caractéristiques des données et de revoir les choix d&#x27;indexation présente d&#x27;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&#x27;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&#x27;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&#x27;autres documents.</a></p><p>Lucene choisit de fusionner des segments de taille similaire car cela permet d&#x27;obtenir une croissance logarithmique de l&#x27;amplification de l&#x27;écriture. Dans le cas d&#x27;un index vectoriel, l&#x27;amplification de l&#x27;écriture est le nombre de fois qu&#x27;un vecteur sera inséré dans un graphique. Lucene essaiera de fusionner les segments par groupes d&#x27;environ 10. Par conséquent, les vecteurs sont insérés dans un graphe environ $1+\frac{9}{10}\log_${10}\left (\frac{n}{n_0}\right )<em> </em>fois, où $n est$ le nombre de vecteurs de l&#x27;index et $n_0$ est le nombre de vecteurs du segment de base attendu. En raison de la croissance logarithmique, l&#x27;amplification de l&#x27;é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&#x27;amplification de l&#x27;é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&#x27;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&#x27;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&#x27;il contient. Nous aimerions utiliser cette information pour accélérer l&#x27;insertion d&#x27;au moins une partie des vecteurs.</p><p>Nous nous concentrons sur le problème de l&#x27;insertion d&#x27;un petit graphe $G_s=(V_s,E_s)$ dans un graphe plus grand $G_l=(V_l,E_l)$, puisqu&#x27;il s&#x27;agit d&#x27;une opération atomique que nous pouvons utiliser pour construire n&#x27;importe quelle politique de fusion.</p><p>La stratégie consiste à trouver un sous-ensemble de sommets de l&#x27;$ensemble J\subset V_s$ à insérer dans le grand graphe. Nous utilisons ensuite la connectivité de ces sommets dans le petit graphe pour accélérer l&#x27;insertion des sommets restants $V_s \setminus J.$ Dans ce qui suit, nous utilisons $N_s(u)$ et $N_l(u)$ pour désigner les voisins d&#x27;un sommet $u$ 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 $G_s$ and $G_l$</code></p><p><code>1$\;\;$Find $J\subset V_s$ to insert into $G_l$ using COMPUTE-JOIN-SET</code><br/><code>2$\;\;$Insert each vertex $u\in J$ into $G_l$</code><br/><code>3$\;\;$for $u\in V_s \setminus J$ do</code><br/><code>4$\;\;\;\;J_u\leftarrow J\cap N_s(u)$</code><br/><code>5$\;\;\;\;E_u\leftarrow \cup_{v\in J_u} N_l(u)$</code><br/><code>6$\;\;\;\;W\leftarrow\,$FAST-SEARCH-LAYER$(J_u, E_u)$</code><br/><code>7$\;\;\;\;neighbors \leftarrow\,$SELECT-NEIGHBORS-HEURISTIC$(u, W)$</code><br/><code>8$\;\;\;\;J\leftarrow J \cup \{u\}$</code></p><p>Nous calculons l&#x27;ensemble $J$ à l&#x27;aide d&#x27;une procédure que nous décrivons ci-dessous (ligne 1). Ensuite, nous insérons chaque sommet de $J$ dans le grand graphe à l&#x27;aide de la procédure d&#x27;insertion HNSW standard (ligne 2). Pour chaque sommet que nous n&#x27;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&#x27;<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&#x27;ensemble de candidats dans la méthode <code>INSERT</code> (Algorithme 1 de l&#x27;article), qui est par ailleurs inchangée. Enfin, nous ajoutons le sommet que nous venons d&#x27;insérer dans $J$ (ligne 8).</p><p>Il est clair que pour que cela fonctionne, chaque sommet dans $V_s \setminus J$ doit avoir au moins un voisin dans $J.$ En fait, nous exigeons que pour chaque sommet dans $u\in V_s \setminus J$ que $|J\cap N_s(u) |\geq k_u$ pour quelque $k_u&lt;$ 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&#x27;un graphique Lucene HNSW.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/44f001b3a1bc6627e172fed5c52a02cfdcb4cd66-1324x898.png" style="width: 100%;" /><p>Nous avons étudié la possibilité d&#x27;utiliser une valeur fixe pour $k_u$ 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><div class="katex">k_u=\max\left(2,\frac{1}{4}|N_s(u)|\right)</div><p>Notez que $|N_s(u)$| est égal au degré du sommet $u$ dans le petit graphe par définition. Le fait d&#x27;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 $J$ avec soin, il nous suffit d&#x27;insérer directement dans $G_l$ $environ \frac |V_s|.{1}{5}$ Plus précisément, nous colorons une arête du graphe si nous insérons exactement l&#x27;un de ses sommets d&#x27;extrémité dans $J.$ Nous savons alors que pour que chaque sommet de $V_s \setminus J$ ait au moins $k_u$ voisins dans $J$, nous devons colorer au moins \sum_ ${u\in V_s\setminus J} k_u$ arêtes. En outre, nous nous attendons à ce que</p><div class="katex">\sum_{u\in V_s\setminus J} k_u\approx \left(|V_s|-|J|\right)\frac{1}{4}\mathbb{E}_U\left[|N_s(U)|\right]</div><p>Ici, \mathbb ${E}_U\left$ [N_s(U)|\right] est le degré moyen des sommets dans le petit graphe. Pour chaque sommet $u\in J$, nous colorons au plus $|N_s(u)|$ arêtes. Par conséquent, le nombre total d&#x27;arêtes que nous prévoyons de colorer est au maximum de $|J|\, \mathbb{E}$_U\left [|N_s(U)|\right]. Nous espérons qu&#x27;en choisissant $J$ avec soin, nous colorerons un nombre d&#x27;arêtes proche de ce nombre et donc, pour couvrir tous les sommets, $|J|$ doit satisfaire à</p><div class="katex">|J|\, \mathbb{E}_U\left[|N_s(U)|\right] =\left(|V_s|-|J|\right)\frac{1}{4}\mathbb{E}_U\left[|N_s(U)|\right]</div><p>Ceci implique que $|J|=\frac{1}{4}(|V_s|-|J|)=\frac{4}{5}\frac$ {1}{4}|V_s|=\frac{1}{5}|V_s|.</p><p>Si <code>SEARCH-LAYER</code> domine le temps d&#x27;exécution, cela signifie que nous pourrions accélérer le temps de fusion jusqu&#x27;à $5 fois.$ Compte tenu de la croissance logarithmique de l&#x27;amplification de l&#x27;é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&#x27;un seul graphique.</p><p>Le risque de cette stratégie est de nuire à la qualité du graphique. Nous avons d&#x27;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&#x27;obtenir des graphes d&#x27;excellente qualité tout en réduisant le temps de fusion d&#x27;un peu plus de 30% en moyenne.</p><h3>Calcul de l&#x27;ensemble de jonction</h3><p>La recherche d&#x27;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&#x27;approche que nous adoptons consiste à sélectionner les sommets un par un pour les ajouter à $J$ dans l&#x27;ordre décroissant des gains. Le gain est défini comme suit :</p><div class="katex">Gain(v)=\max(k_v-c(v),0)+\sum_{u\in N_s(v)\setminus J} 1\left\{c(u)<k_u\right\}</div><p>Ici, $c(v)$ représente le nombre de voisins d&#x27;un vecteur $v$ dans $J$ et $1\{\cdot\}$ est la fonction indicatrice. Le gain comprend la variation du nombre de sommets que nous avons ajoutés à $J$, c&#x27;est-à-dire \max $(k_v-c(v),0)$, 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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fe11cd55a7d94d9e0b0f4d47ea309a99075c355d-540x474.png" style="width: 100%;" /><p>Nous conservons l&#x27;état suivant pour chaque sommet $v :$</p><ol><li>S&#x27;il est périmé,</li><li>Son gain $Gain(v)$,</li><li>Le nombre de sommets adjacents dans $J$ est noté $c(v),$</li><li>Un nombre aléatoire dans l&#x27;intervalle [0,1] qui est utilisé pour départager les ex-aequo.</li></ol><p>Le pseudo-code pour le calcul de l&#x27;ensemble de jonction est le suivant.</p><p><code>COMPUTE-JOIN-SET</code></p><p><code>Inputs $G_s$</code></p><p><code>1$\;\;C\leftarrow\emptyset$</code><br/><code>2$\;\;Gain_{exit}\leftarrow 0$</code><br/><code>3$\;\;$for $u\in G_s$ do</code><br/><code>4$\;\;\;\;C\leftarrow C \cup \{(\text{false}, k_u+deg(u), 0, \text{rand in }[0,1])\}$</code><br/><code>5$\;\;\;\;Gain_{exit}\leftarrow Gain_{exit}+k_u$</code><br/><code>6$\;\;Gain_{tot}\leftarrow 0$</code><br/><code>7$\;\;$while $Gain_{tot}&lt;Gain_{exit}$ do<br/>8$\;\;\;\;v^*\leftarrow\,$ maximum gain vertex in $C$</code><br/><code>9$\;\;\;\;$Remove the state for $v^*$ from $C$</code><br/><code>10$\;\;\;$if $v^*$ is not stale then</code><br/><code>11$\;\;\;\;\;J\leftarrow J\cup\{v^*\}$</code><br/><code>12$\;\;\;\;\;Gain_{tot}\leftarrow Gain_{tot}+Gain(v^*)$</code><br/><code>13$\;\;\;\;\;$for $u \in N_s(v^*)$ do</code><br/><code>14$\;\;\;\;\;\;\;$mark $u$ as stale if $c(v^*)&lt;k_{v^*}$</code><br/><code>15$\;\;\;\;\;\;\;$mark neighbors of $u$ stale if $c(u)=k_u-1$<br/>16$\;\;\;\;\;\;\;c(u)\leftarrow c(u)+1$<br/>17$\;\;\;$else<br/>18$\;\;\;\;\;Gain(v^*)\leftarrow \max(k_v-c(v^*),0)+\sum_{u\in N_s(v^*)\setminus J} 1\left\{c(u)&lt;k_u\right\}$<br/>19$\;\;\;\;\;$if $Gain(v^*)&gt;0$ then<br/>20$\;\;\;\;\;\;\;C\leftarrow C \cup \{(\text{false},Gain(v^*),c(v^*),\text{copy rand})\}$<br/>21$\;\;$return $J$</code></p><p>Nous commençons par initialiser l&#x27;état dans les lignes 1 à 5.</p><p>À chaque itération de la boucle principale, nous extrayons d&#x27;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 à $J$, nous affectons le gain d&#x27;autres sommets :</p><ol><li>Puisque tous ses voisins ont un voisin supplémentaire en $J$, leurs gains peuvent changer (ligne 14)</li><li>Si l&#x27;un de ses voisins est maintenant entièrement couvert, tous les gains de ses voisins peuvent changer (lignes 14-16).</li></ol><p>Nous recalculons les gains paresseusement, c&#x27;est-à-dire que nous ne recalculons le gain d&#x27;un sommet que si nous voulons l&#x27;insérer dans $J$ (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 à $J$ pour déterminer quand sortir. En outre, pendant que $Gain_{tot}&lt; Gain_$ {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>quora-E5-small : 522931 documents, 384 dimensions et utilise la similarité cosinus,</li><li>cohere-wikipedia-v2 : 1M documents, 768 dimensions et utilise la similarité cosinus,</li><li>gist : 1M documents, 960 dimensions et utilise la distance euclidienne, et</li><li>cohere-wikipedia-v3 : 1M documents, 1024 dimensions et utilise le produit intérieur maximum.</li></ol><p>Pour chaque ensemble de données, nous évaluons deux niveaux de quantification :</p><ol><li>int8 - qui utilise un entier de 1 octet par dimension et</li><li>BBQ - qui utilise un seul bit par dimension.</li></ol><p>Enfin, pour chaque expérience, nous avons évalué la qualité de la recherche à deux profondeurs d&#x27;extraction et nous l&#x27;avons examinée après la construction de l&#x27;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&#x27;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&#x27;indexation : <strong>1,$28$</strong>fois</p><p>Accélération de la fusion forcée : <strong>1,$72$</strong>fois</p><p>Cela correspond à la répartition suivante des durées d&#x27;exécution</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5f9f6d487b4c74c4998aa47465912c1a9743e928-734x479.png" style="width: 100%;" /><p>Par souci d&#x27;exhaustivité, les horaires exacts sont les suivants</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Index</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Fusionner</td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Ensemble de données</td><td style="border: 1px solid #ccc; padding: 4px;">ligne de base</td><td style="border: 1px solid #ccc; padding: 4px;">candidat</td><td style="border: 1px solid #ccc; padding: 4px;">Développer</td><td style="border: 1px solid #ccc; padding: 4px;">candidat</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">quora-E5-petit</td><td style="border: 1px solid #ccc; padding: 4px;">112.41s</td><td style="border: 1px solid #ccc; padding: 4px;">81.55s</td><td style="border: 1px solid #ccc; padding: 4px;">113.81s</td><td style="border: 1px solid #ccc; padding: 4px;">70.87s</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">wiki-cohere-v2</td><td style="border: 1px solid #ccc; padding: 4px;">158.1s</td><td style="border: 1px solid #ccc; padding: 4px;">122.95s</td><td style="border: 1px solid #ccc; padding: 4px;">425.20s</td><td style="border: 1px solid #ccc; padding: 4px;">239.28s</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">liste</td><td style="border: 1px solid #ccc; padding: 4px;">141.82s</td><td style="border: 1px solid #ccc; padding: 4px;">119.26s</td><td style="border: 1px solid #ccc; padding: 4px;">536.07s</td><td style="border: 1px solid #ccc; padding: 4px;">279.05s</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">wiki-cohere-v3</td><td style="border: 1px solid #ccc; padding: 4px;">211.86s</td><td style="border: 1px solid #ccc; padding: 4px;">168.22s</td><td style="border: 1px solid #ccc; padding: 4px;">654.97s</td><td style="border: 1px solid #ccc; padding: 4px;">414.12s</td>
                    </tr>
              </tbody>
            </table>
          <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&#x27;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&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9195297f19f90b172d832ba56bdada7b0d8a768b-985x392.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c866f32d579ae2b841e92ca733dd29c0e214ac6b-986x386.png" style="width: 100%;" /><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&#x27;indexation : <strong>1,$33$</strong>fois</p><p>Accélération de la fusion forcée : <strong>1,$34$</strong>fois</p><p>Cela correspond à la répartition suivante des durées d&#x27;exécution</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/11be0cf6b63480dc703409329357ea4847b55051-740x415.png" style="width: 100%;" /><p>Par souci d&#x27;exhaustivité, les horaires exacts sont les suivants</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Index</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Fusionner</td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Ensemble de données</td><td style="border: 1px solid #ccc; padding: 4px;">ligne de base</td><td style="border: 1px solid #ccc; padding: 4px;">candidat</td><td style="border: 1px solid #ccc; padding: 4px;">Développer</td><td style="border: 1px solid #ccc; padding: 4px;">candidat</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">quora-E5-petit</td><td style="border: 1px solid #ccc; padding: 4px;">70.71s</td><td style="border: 1px solid #ccc; padding: 4px;">58.25s</td><td style="border: 1px solid #ccc; padding: 4px;">59.38s</td><td style="border: 1px solid #ccc; padding: 4px;">40.15s</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">wiki-cohere-v2</td><td style="border: 1px solid #ccc; padding: 4px;">203.08s</td><td style="border: 1px solid #ccc; padding: 4px;">142.27s</td><td style="border: 1px solid #ccc; padding: 4px;">107.27s</td><td style="border: 1px solid #ccc; padding: 4px;">85.68s</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">liste</td><td style="border: 1px solid #ccc; padding: 4px;">110.35s</td><td style="border: 1px solid #ccc; padding: 4px;">105.52s</td><td style="border: 1px solid #ccc; padding: 4px;">323.66s</td><td style="border: 1px solid #ccc; padding: 4px;">202.2s</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">wiki-cohere-v3</td><td style="border: 1px solid #ccc; padding: 4px;">313.43s</td><td style="border: 1px solid #ccc; padding: 4px;">190.63s</td><td style="border: 1px solid #ccc; padding: 4px;">165.98s</td><td style="border: 1px solid #ccc; padding: 4px;">159.95s</td>
                    </tr>
              </tbody>
            </table>
          <p>Pour les indices de segments multiples, le candidat est meilleur pour presque tous les ensembles de données, à l&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7da1be27bab5a7f5f9c9a1882ea35761a4a0ba5c-973x383.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a26af30a56c55a12addee7e4fb7e8f606f366668-979x386.png" style="width: 100%;" /><h3>Conclusion</h3><p>L&#x27;algorithme présenté dans ce blog sera disponible dans la prochaine version de Lucene 10.2, ainsi que dans la version d&#x27;Elasticsearch qui est basée sur cette dernière. Les utilisateurs pourront profiter de l&#x27;amélioration des performances de fusion et de la réduction du temps de construction de l&#x27;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>
        </item>
        <item>
            <title><![CDATA[Générer des filtres et des facettes à l'aide de la ML]]></title>
            <description><![CDATA[Exploration des avantages et des inconvénients de l'automatisation de la création de filtres et de facettes dans une expérience de recherche à l'aide de modèles ML par rapport à l'approche classique codée en dur.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/filters-facets-using-ml</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/filters-facets-using-ml</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Thu, 03 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f196643d573614fe5124705c7e4db9bfce004b0-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<a href="https://www.youtube.com/watch/zrD1Q_KKxUw">Watch video on YouTube</a><p>Les filtres et les facettes sont des mécanismes utilisés pour affiner les résultats de la recherche et aider les utilisateurs à trouver plus rapidement le contenu ou les produits pertinents. Dans l&#x27;approche classique, les règles sont définies manuellement. Par exemple, dans un catalogue de films, des attributs tels que le genre sont prédéfinis pour être utilisés dans les filtres et les facettes. D&#x27;autre part, les modèles d&#x27;intelligence artificielle permettent d&#x27;extraire automatiquement de nouveaux attributs à partir des caractéristiques des films, ce qui rend le processus plus dynamique et plus personnalisé. Dans ce blog, nous explorons les avantages et les inconvénients de chaque méthode, en mettant en évidence leurs applications et leurs défis.</p><h2>Filtres vs facettes</h2><p>Avant de commencer, définissons ce que sont les filtres et les facettes. Les <strong>filtres</strong> sont des attributs prédéfinis utilisés pour restreindre un ensemble de résultats. Sur une place de marché, par exemple, des filtres sont disponibles avant même qu&#x27;une recherche ne soit effectuée. L&#x27;utilisateur peut sélectionner une catégorie, telle que <strong>&quot;Jeux vidéo&quot;</strong>, avant de rechercher <strong>&quot;PS5&quot;</strong>, ce qui permet d&#x27;affiner la recherche à un sous-ensemble plus spécifique plutôt qu&#x27;à l&#x27;ensemble de la base de données. Cela augmente considérablement les chances d&#x27;obtenir des résultats plus pertinents.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5ed8868fa5017d034e1273e35c884a5430afdf3c-1600x937.png" style="width: 100%;" /><p>Les <strong>facettes</strong> fonctionnent de la même manière que les filtres, mais elles ne sont disponibles qu&#x27;une fois la recherche effectuée. En d&#x27;autres termes, la recherche renvoie des résultats et, sur la base de ceux-ci, une nouvelle liste d&#x27;options d&#x27;affinage est générée. Par exemple, lors de la recherche d&#x27;une console PS5, des aspects tels que la <strong>capacité de</strong> stockage, les <strong>frais d&#x27;expédition</strong> et la <strong>couleur</strong> peuvent être affichés pour aider les utilisateurs à choisir le produit idéal.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c5633fcc5b6fbb916110faf32144d8d43572e33a-1600x937.png" style="width: 100%;" /><p>Maintenant que nous avons défini les filtres et les facettes, examinons l&#x27;impact de l&#x27;approche classique et de l&#x27;approche basée sur l&#x27;apprentissage automatique sur leur mise en œuvre et leur utilisation. Chaque méthode présente des avantages et des inconvénients qui influencent l&#x27;efficacité de la recherche.</p><h2>Approche classique des filtres et des facettes</h2><p>Dans cette approche, les filtres et les facettes sont définis manuellement sur la base de règles prédéfinies. Cela signifie que les attributs disponibles pour affiner la recherche sont fixés et planifiés à l&#x27;avance, en tenant compte de la structure du catalogue et des besoins de l&#x27;utilisateur.</p><p>Par exemple, sur une place de marché, les catégories telles que &quot;Électronique&quot; ou &quot;Mode&quot; peuvent avoir des filtres spécifiques tels que la marque, le format et la fourchette de prix. Ces règles sont créées de manière statique, ce qui garantit la cohérence de l&#x27;expérience de recherche, mais nécessite des ajustements manuels lorsque de nouveaux produits ou de nouvelles catégories apparaissent.</p><p>Bien que cette approche permette de prévoir et de contrôler les filtres et les facettes affichés, elle peut s&#x27;avérer limitée lorsque de nouvelles tendances apparaissent et exigent un affinement dynamique.</p><p><strong>Pour :</strong></p><ul><li><strong>Prévisibilité et contrôle :</strong> Les filtres et les facettes étant définis manuellement, la gestion devient plus facile.</li><li><strong>Faible complexité :</strong> Il n&#x27;est pas nécessaire de former des modèles.</li><li><strong>Facilité de maintenance :</strong> Les règles étant prédéfinies, les ajustements et les corrections peuvent être effectués rapidement.</li></ul><p><strong>Cons :</strong></p><ul><li><strong>Réindexation nécessaire pour les nouveaux filtres :</strong> Chaque fois qu&#x27;un nouvel attribut doit être utilisé comme filtre, l&#x27;ensemble du jeu de données doit être réindexé pour s&#x27;assurer que les documents contiennent cette information.</li><li><strong>Absence d&#x27;adaptation dynamique :</strong> Les filtres sont statiques et ne s&#x27;adaptent pas automatiquement aux changements de comportement des utilisateurs.</li></ul><h3>Mise en œuvre des filtres/facettes - Approche classique</h3><p>Dans <strong>Dev Tools, Kibana</strong>, nous allons créer une démonstration de filtres/facettes en utilisant l&#x27;<strong>approche classique.</strong></p><p>Tout d&#x27;abord, nous définissons la correspondance pour structurer l&#x27;index :</p><pre><code>PUT videogames
{
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "brand": { "type": "keyword" },
      "storage": { "type": "keyword" },
      "price": { "type": "float" },
      "description": { "type": "text" }
    }
  }
}</code></pre><p>Les champs <strong>marque</strong> et <strong>stockage</strong> sont définis comme des <strong>mots-clés</strong>, ce qui permet de les utiliser directement dans les agrégations<strong>(facettes)</strong>. Le champ <strong>prix</strong> est de type <strong>flottant</strong>, ce qui permet de créer des <strong>fourchettes de prix</strong>.</p><p>L&#x27;étape suivante consiste à indexer les données relatives aux produits :</p><pre><code>POST videogames/_bulk
{ "index": { "_id": 1 } }
{ "name": "Play Station 5", "brand": "Sony", "storage": "1TB", "price": 499.99, "description": "Stunning Gaming: Marvel at stunning graphics and experience the features of the new PS5. Breathtaking Immersion: Discover a deeper gaming experience with support for haptic feedback, adaptive triggers, and 3D Audio technology. Slim Design: With the PS5 Digital Edition, gamers get powerful gaming technology in a sleek, compact design. 1TB of Storage: Have your favorite games ready and waiting for you to play with 1TB of built-in SSD storage. Backward Compatibility and Game Boost: The PS5 console can play over 4,000 PS4 games. With Game Boost, you can even enjoy faster, smoother frame rates in some of the best PS4 console games." }
{ "index": { "_id": 2 } }
{ "name": "Xbox Series X", "brand": "Microsoft", "storage": "1TB", "price": 499.99, "description": "Fastest, most powerful Xbox console ever. Play thousands of titles: Every game looks and plays better on Xbox Series X. At the heart of Series X is the Xbox Velocity. Architecture, which combines a custom SSD and built-in software to significantly reduce load times in and out of game. Switch between multiple games in an instant with Quick Resume. Explore new worlds and experience the action like never before with an unparalleled 12 teraflops of graphics processing power. Enjoy 4K gaming at up to 120 frames per second, premium advanced 3D sound, and more. 4K at 120 FPS: requires compatible content and display X version - with disc drive" }
{ "index": { "_id": 3 } }
{ "name": "Nintendo Switch", "brand": "Nintendo", "storage": "512GB", "price": 299.99, "description": "SHARPER, VIBRANT VISUALS. The new 7-inch screen on the Nintendo Switch OLED takes your gaming to the next level: vibrant colors with sharp contrasts for every moment. INTEGRATED GAMEPLAY. Enjoy the console's many multiplayer modes and connect with other players. Online or locally, the fun on the Nintendo Switch is guaranteed. ENJOY IMMERSION FOR LONGER. In addition to delivering an unparalleled experience, thanks to its improved audio, the Nintendo Switch has a rechargeable battery while you play. From 4.5 hours to 9 hours of battery life. INCLUDES SUPER MARIO BROS. WONDER. Transform your world with the phenomenal flowers in this new Mario game, full of amazing adventures, power-ups and new abilities. NINTENDO SWITCH ONLINE SUBSCRIPTION. Access online games, play with friends and enjoy the exclusive benefits of the Nintendo Switch Online subscription." }
{ "index": { "_id": 4 } }
{ "name": "Steam Deck", "brand": "Valve", "storage": "512GB", "price": 399.99, "description": "You can save games, apps, photos and videos without worrying about space. High-Level Performance: The 4-core processor and graphics ensure a dynamic experience and fast responses. High-Definition Images: Smooth transitions and sharp images provide complete immersion in the game. Wireless Connectivity: Wi-Fi technology allows you to play wherever you want, without wires or cables limiting your fun" }
{ "index": { "_id": 5 } }
{ "name": "Nintendo Switch Lite", "brand": "Nintendo", "storage": "512GB", "price": 299.99, "description": "MADE TO BE PORTABLE. Nintendo Switch Lite is designed specifically for portable gaming. The console lets you jump into your favorite games wherever you are. COMPACT AND LIGHTWEIGHT. With its sleek, lightweight design, this console is ready to hit the road wherever you are. COMPATIBLE GAMES. The Nintendo Switch Lite system plays the library of Nintendo Switch games that work in handheld mode. A WORLD OF COLOR TO CHOOSE FROM. Available in a variety of vibrant and unique colors, Nintendo Switch Lite lets you bring even more personality wherever you go." }</code></pre><p>Retrouvons maintenant des facettes classiques en regroupant les résultats par marque, rangement et gamme de prix. Dans la requête, la taille:0 a été définie. Dans ce scénario, l&#x27;objectif est de récupérer uniquement les résultats de l&#x27;agrégation sans inclure les documents correspondant à la requête.</p><pre><code>POST videogames/_search
{
  "size": 0,
  "aggs": {
    "brands": {
      "terms": { "field": "brand" }
    },
    "storage_sizes": {
      "terms": { "field": "storage" }
    },
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 300 },   
          { "from": 300, "to": 500 },  
          { "from": 500 }  
        ]
      }
    }
  }
}</code></pre><p>La réponse comprendra des comptes pour la <strong>marque</strong>, le <strong>stockage</strong> et le <strong>prix</strong>, ce qui permettra de créer des filtres et des facettes.</p><pre><code>"aggregations": {
   "brands": {
     "doc_count_error_upper_bound": 0,
     "sum_other_doc_count": 0,
     "buckets": [
       {
         "key": "Microsoft",
         "doc_count": 1
       },
       {
         "key": "Nintendo",
         "doc_count": 1
       },
       {
         "key": "Sony",
         "doc_count": 1
       },
       {
         "key": "Valve",
         "doc_count": 1
       }
     ]
   },
   "storage_sizes": {
     "doc_count_error_upper_bound": 0,
     "sum_other_doc_count": 0,
     "buckets": [
       {
         "key": "1TB",
         "doc_count": 2
       },
       {
         "key": "512GB",
         "doc_count": 2
       }
     ]
   },
   "price_ranges": {
     "buckets": [
       {
         "key": "*-300.0",
         "to": 300,
         "doc_count": 1
       },
       {
         "key": "300.0-500.0",
         "from": 300,
         "to": 500,
         "doc_count": 3
       },
       {
         "key": "500.0-*",
         "from": 500,
         "doc_count": 0
       }
     ]
   }
 }</code></pre><h2>Approche basée sur le Machine Learning et l’IA pour les filtres et les facettes</h2><p>Dans cette approche, les modèles d&#x27;apprentissage automatique (ML), y compris les techniques d&#x27;intelligence artificielle (IA), analysent les attributs des données pour générer des filtres et des facettes pertinents. Au lieu de s&#x27;appuyer sur des règles prédéfinies, la ML/AI exploite les caractéristiques des données indexées. Cela permet la découverte dynamique de nouvelles facettes et de nouveaux filtres.</p><p><strong>Pour :</strong></p><ul><li><strong>Mises à jour automatiques :</strong> Les nouveaux filtres et facettes sont générés automatiquement, sans qu&#x27;il soit nécessaire de procéder à des ajustements manuels.</li><li><strong>Découverte de nouveaux attributs :</strong> Il peut identifier des caractéristiques de données <strong>précédemment non prises en compte </strong>en tant que filtres, enrichissant ainsi l&#x27;expérience de recherche.</li><li><strong>Réduction des efforts manuels :</strong> L&#x27;équipe n&#x27;a pas besoin de définir et de mettre à jour en permanence les règles de filtrage, car l&#x27;IA apprend à partir des données disponibles.</li></ul><p><strong>Cons :</strong></p><ul><li><strong>Complexité de la maintenance :</strong> L&#x27;utilisation de modèles peut nécessiter une prévalidation pour garantir la cohérence des filtres générés.</li><li><strong>Nécessite une expertise en ML et en IA :</strong> La solution exige des professionnels qualifiés pour affiner et surveiller les performances du modèle.</li><li><strong>Risque de filtres non pertinents :</strong> Si le modèle n&#x27;est pas bien calibré, il peut générer des facettes qui ne sont pas utiles aux utilisateurs.</li><li><strong>Coût :</strong> L&#x27;utilisation de la ML et de l&#x27;IA peut nécessiter des services tiers, ce qui augmente les coûts opérationnels.</li></ul><p>Il convient de noter que même avec un modèle bien calibré et une invite bien rédigée, les facettes générées doivent toujours passer par une étape de révision. Cette validation peut être manuelle ou basée sur des règles de modération, garantissant que le contenu est approprié et sûr. Bien qu&#x27;il ne s&#x27;agisse pas nécessairement d&#x27;un inconvénient, il est important de s&#x27;assurer de la qualité et de l&#x27;adéquation des facettes avant de les mettre à la disposition des utilisateurs.</p><h3>Mise en œuvre de filtres/façades - Approche IA</h3><p>Dans cette démonstration, nous utiliserons un modèle d&#x27;intelligence artificielle pour analyser automatiquement les caractéristiques des produits et suggérer des attributs pertinents. Avec une invite bien structurée, nous extrayons des informations du catalogue et les transformons en filtres et facettes. Nous présentons ci-dessous chaque étape du processus.</p><p>Dans un premier temps, nous utiliserons l&#x27;<strong>API Inference</strong> pour enregistrer un point de terminaison en vue d&#x27;une intégration avec un service de ML. Vous trouverez ci-dessous un exemple d&#x27;intégration avec le <strong>service OpenAI</strong>.</p><pre><code>PUT _inference/completion/generate_filter_ia
{
   "service": "openai",
   "service_settings": {
       "api_key": "your-key",
       "model_id": "gpt-4o-mini"
   }
}</code></pre><p>Nous définissons maintenant le pipeline permettant d&#x27;exécuter l&#x27;invite et d&#x27;obtenir les nouveaux filtres générés par le modèle.</p><pre><code>PUT /_ingest/pipeline/generate_filter_ai
{
   "processors": [
     {
       "script": {
         "source": """ctx.prompt = "You are an expert in data organization for search and product categorization. Your task is to analyze the following product and identify the best dynamic facets that can be used in an e-commerce search experience. Product: " + ctx.name + "description: " + ctx.description + "Instructions: - Analyze the product name and description. - Extract only the dynamic facets (technological features or product characteristics that can be inferred from the description, try to create max 3 facets by characteristics found). Put the values into an array. Using key and value, e.g. dynamic_facets: [{ \"name\": \"Gaming Experience\", \"value\": \"Haptic Feedback\" },{ \"name\": \"Gaming Experience\", \"value\": \"Adaptive Triggers\" } - Return only a JSON."
         """
       }
     },
     {
       "inference": {
         "model_id": "generate_filter_ia",
         "input_output": {
           "input_field": "prompt",
           "output_field": "result"
         }
       }
     },
     {
       "gsub": {
         "field": "result",
         "pattern": "```json",
         "replacement": ""
       }
     },
     {
       "json" : {
         "field" : "result",
         "strict_json_parsing": false,
         "add_to_root" : true
       }
     },
     {
       "remove": {
         "field": "result"
       }
     },
     {
       "remove": {
         "field": "prompt"
       }
     }
   ]
}</code></pre><p>Lancer une simulation de ce pipeline pour le produit &quot;PlayStation 5&quot;, avec la description suivante :</p><p><em>Des jeux époustouflants : Admirez des graphismes époustouflants et découvrez les fonctionnalités de la nouvelle PS5.</em></p><p><em>Une immersion à couper le souffle : Découvrez une expérience de jeu plus profonde grâce à la prise en charge du retour haptique, des déclencheurs adaptatifs et de la technologie audio 3D.</em></p><p><em>Un design élancé : Avec la PS5 Digital Edition, les joueurs bénéficient d&#x27;une technologie de jeu puissante dans un design élégant et compact.</em></p><p><em>1 To de stockage : Vos jeux préférés sont prêts à être joués grâce à 1 To de stockage SSD intégré.</em></p><p><em>Rétrocompatibilité et boost de jeux : La console PS5 peut lire plus de 4 000 jeux PS4. Avec Game Boost, vous pouvez même profiter de taux de rafraîchissement plus rapides et plus fluides dans certains des meilleurs jeux de la console PS4.</em></p><p>Observons la sortie rapide générée par cette simulation.</p><pre><code>{
 "docs": [
   {
     "doc": {
       "_index": "index",
       "_version": "-3",
       "_id": "1",
       "_source": {
         "name": "Play Station 5",
         "result": """```json
{
 "dynamic_facets": [
   { "name": "Storage Capacity", "value": "1TB SSD" },
   { "name": "Graphics Technology", "value": "Stunning Graphics" },
   { "name": "Audio Technology", "value": "3D Audio" }
 ]
}
```""",
         "description": "Stunning Gaming: Marvel at stunning graphics and experience the features of the new PS5. Breathtaking Immersion: Discover a deeper gaming experience with support for haptic feedback, adaptive triggers, and 3D Audio technology. Slim Design: With the PS5 Digital Edition, gamers get powerful gaming technology in a sleek, compact design. 1TB of Storage: Have your favorite games ready and waiting for you to play with 1TB of built-in SSD storage. Backward Compatibility and Game Boost: The PS5 console can play over 4,000 PS4 games. With Game Boost, you can even enjoy faster, smoother frame rates in some of the best PS4 console games.",
         "model_id": "generate_filter_ia",
         "prompt": """You are an expert in data organization for search and product categorization. Your task is to analyze the following product and identify the best dynamic facets that can be used in an e-commerce search experience. Product: Play Station 5description: Stunning Gaming: Marvel at stunning graphics and experience the features of the new PS5. Breathtaking Immersion: Discover a deeper gaming experience with support for haptic feedback, adaptive triggers, and 3D Audio technology. Slim Design: With the PS5 Digital Edition, gamers get powerful gaming technology in a sleek, compact design. 1TB of Storage: Have your favorite games ready and waiting for you to play with 1TB of built-in SSD storage. Backward Compatibility and Game Boost: The PS5 console can play over 4,000 PS4 games. With Game Boost, you can even enjoy faster, smoother frame rates in some of the best PS4 console games.Instructions: - Analyze the product name and description. - Extract only the dynamic facets (technological features or product characteristics that can be inferred from the description, try create max 3 facets by characteristics found). Put the values like arrays. Using key and value, e.g. dynamic_facets: [{ "name": "Gaming Experience", "value": "Haptic Feedback" },{ "name": "Gaming Experience", "value": "Adaptive Triggers" } - Return only a JSON."""
       },
       "_ingest": {
         "timestamp": "2025-03-19T22:14:32.0161803Z"
       }
     }
   }
 ]
}</code></pre><p>Un nouveau champ, <strong>dynamic_facets</strong>, sera ajouté au nouvel index pour stocker les facettes générées par l&#x27;IA.</p><pre><code>PUT videogames_1
{
 "mappings": {
   "properties": {
     "name": { "type": "text" },
     "brand": { "type": "keyword" },
     "storage": { "type": "keyword" },
     "price": { "type": "float" },
     "description": { "type": "text" },
     "dynamic_facets": { "type": "nested",
     "properties": { "name": { "type": "keyword" },
                     "value": { "type": "keyword" } } }
   }
 }
}</code></pre><p>En utilisant l&#x27;<strong>API Reindex</strong>, nous allons réindexer l&#x27;index <strong>videogames</strong> en <strong>videogames_1</strong>, en appliquant le pipeline <strong>generate_filter_ai</strong> pendant le processus. Ce pipeline génère automatiquement des facettes dynamiques lors de l&#x27;indexation.</p><pre><code>POST _reindex?wait_for_completion=false
{
 "source": {
   "index": "videogames"
 },
 "dest": {
   "index": "videogames_1",
   "pipeline": "generate_filter_ai"
 }
}</code></pre><p>Nous allons maintenant lancer une recherche et obtenir les nouveaux filtres :</p><pre><code>GET videogames_1/_search
{
 "size": 0,
 "query": {
   "match": {
     "name": "nintendo"
   }
 },
 "aggs": {
   "dynamic_facets": {
     "nested": {
       "path": "dynamic_facets"
     },
     "aggs": {
       "facets": {
         "terms": {
           "field": "dynamic_facets.name"
         },
         "aggs": {
           "facets": {
             "terms": {
               "field": "dynamic_facets.value"
             }
           }
         }
       }
     }
   }
 }
}</code></pre><p>Résultats :</p><pre><code>"aggregations": {
   "dynamic_facets": {
     "doc_count": 3,
     "facets": {
       "doc_count_error_upper_bound": 0,
       "sum_other_doc_count": 0,
       "buckets": [
         {
           "key": "Frame Rate",
           "doc_count": 1,
           "facets": {
             "doc_count_error_upper_bound": 0,
             "sum_other_doc_count": 0,
             "buckets": [
               {
                 "key": "120 FPS",
                 "doc_count": 1
               }
             ]
           }
         },
         {
           "key": "Gaming Resolution",
           "doc_count": 1,
           "facets": {
             "doc_count_error_upper_bound": 0,
             "sum_other_doc_count": 0,
             "buckets": [
               {
                 "key": "4K",
                 "doc_count": 1
               }
             ]
           }
         },
         {
           "key": "Graphics Processing Power",
           "doc_count": 1,
           "facets": {
             "doc_count_error_upper_bound": 0,
             "sum_other_doc_count": 0,
             "buckets": [
               {
                 "key": "12 Teraflops",
                 "doc_count": 1
               }
             ]
           }
         }
       ]
     }
   }
 }</code></pre><p>Pour illustrer la mise en œuvre des facettes, nous présentons ci-dessous une interface simple :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/12b6d9d4f4d0985848d92841545fd22b7253ae6d-1600x1288.png" style="width: 100%;" /><p>Le code de l&#x27;interface utilisateur présenté est <a href="https://gist.github.com/andreluiz1987/06d9ec1b381e942e9def0e969bd811a0">ici.</a></p><h2>Conclusion</h2><p>Les deux approches de la création de filtres et de facettes ont leurs avantages et leurs inconvénients. L&#x27;approche classique, basée sur des règles manuelles, permet de contrôler et de réduire les coûts, mais elle nécessite des mises à jour constantes et ne s&#x27;adapte pas de manière dynamique aux nouveaux produits ou aux nouvelles fonctionnalités.</p><p>D&#x27;autre part, l&#x27;approche basée sur l&#x27;IA et l&#x27;apprentissage automatique automatise l&#x27;extraction des facettes, ce qui rend la recherche plus flexible et permet de découvrir de nouveaux attributs sans intervention manuelle. Toutefois, cette approche peut être plus complexe à mettre en œuvre et à maintenir, et nécessite un étalonnage pour garantir des résultats cohérents.</p><p>Le choix entre l&#x27;approche classique et l&#x27;approche basée sur l&#x27;IA dépend des besoins et de la complexité de l&#x27;entreprise. Pour les scénarios plus simples, où les attributs des données sont stables et prévisibles, l&#x27;approche classique peut être plus efficace et plus facile à maintenir, en évitant les coûts inutiles liés à l&#x27;infrastructure et aux modèles d&#x27;IA. D&#x27;autre part, l&#x27;utilisation de la ML/AI pour extraire les facettes peut apporter une valeur ajoutée significative, en améliorant l&#x27;expérience de recherche et en rendant le filtrage plus intelligent.</p><p>L&#x27;important est d&#x27;évaluer si l&#x27;automatisation justifie l&#x27;investissement ou si une solution plus traditionnelle répond déjà efficacement aux besoins de l&#x27;entreprise.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Utilisation des modèles Amazon Nova dans Elasticsearch]]></title>
            <description><![CDATA[Découvrez comment utiliser les modèles Amazon Nova dans Elasticsearch pour extraire automatiquement le sentiment, l'authenticité, les résumés et les mots-clés des avis produits dans Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/amazon-nova-models-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/amazon-nova-models-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 02 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/304713c48b568e17d0bb56b19edb28769f7801b3-721x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Dans cet article, nous aborderons la famille de modèles d&#x27;IA d&#x27;Amazon, Amazon Nova, et apprendrons à l&#x27;utiliser avec Elasticsearch.</p><h2>À propos d&#x27;Amazon Nova</h2><p>Amazon Nova est une famille de modèles d&#x27;intelligence artificielle d&#x27;Amazon, disponibles sur Amazon Bedrock et conçus pour offrir de hautes performances et un bon rapport coût-efficacité. Ces modèles fonctionnent avec des entrées texte, image et vidéo, génèrent des sorties textuelles et sont optimisés pour répondre à différents besoins en termes de précision, de vitesse et de coût.</p><h3>Principaux modèles de l&#x27;Amazon Nova</h3><ul><li>Amazon Nova Micro : Axé exclusivement sur le texte, ce modèle rapide et économique est idéal pour la traduction, le raisonnement, la complétion de codes et la résolution de problèmes mathématiques. Sa production dépasse les 200 jetons par seconde, ce qui en fait un outil idéal pour les applications nécessitant des réponses instantanées.</li><li>Amazon Nova Lite : un modèle multimodal économique capable de traiter rapidement des images, des vidéos et du texte. Il se distingue par sa rapidité et sa précision, et convient plus particulièrement aux applications interactives et à fort volume où le coût est un facteur déterminant.</li><li>Amazon Nova Pro : L&#x27;option la plus avancée, combinant haute précision, vitesse et rentabilité. Idéal pour les tâches complexes telles que le résumé de vidéos, les questions et réponses, le développement de logiciels et les agents d&#x27;intelligence artificielle. Les avis d&#x27;experts attestent de son excellence en matière de compréhension textuelle et visuelle, ainsi que de sa capacité à suivre des instructions et à exécuter des flux de travail automatisés.</li></ul><p>Les modèles Amazon Nova sont adaptés à une variété d&#x27;applications, de la création de contenu et de l&#x27;analyse de données au développement de logiciels et à l&#x27;automatisation des processus alimentés par l&#x27;IA.</p><p>Ci-dessous, nous allons montrer comment utiliser les modèles Amazon Nova en conjonction avec Elasticsearch pour l&#x27;analyse automatisée des avis sur les produits.</p><p>Ce que nous ferons :</p><ol><li>Créer un point de terminaison via Inference API, intégrant Amazon Bedrock avec Elasticsearch.</li><li>Créer un pipeline à l&#x27;aide du processeur d&#x27;inférence, qui fera des appels au point d&#x27;extrémité de l&#x27;API d&#x27;inférence.</li><li>Indexer les avis sur les produits et générer automatiquement une analyse des avis à l&#x27;aide du pipeline.</li><li>Analyser les résultats de l&#x27;intégration.</li></ol><h2>Création d&#x27;un point de terminaison dans l&#x27;API d&#x27;inférence avec Amazon Nova Lite</h2><p>Tout d&#x27;abord, nous configurons l&#x27;API Inference pour intégrer Amazon Bedrock à Elasticsearch. Nous définissons Amazon Nova Lite, id <strong>amazon.nova-lite-v1:0,</strong> comme le modèle à utiliser, car il offre un équilibre entre la vitesse, la précision et le coût.</p><p><strong>Remarque :</strong> vous devez disposer d&#x27;informations d&#x27;identification valides pour utiliser Amazon Bedrock. Vous pouvez consulter la documentation relative à l&#x27;obtention des clés d&#x27;accès <a href="https://docs.aws.amazon.com/keyspaces/latest/devguide/create.keypair.html">ici :</a></p><pre><code>PUT _inference/completion/bedrock_completion_amazon_nova-lite
{
   "service": "amazonbedrock",
   "service_settings": {
       "access_key": "#access_key#",
       "secret_key": "#secret_key#",
       "region": "us-east-1",
       "provider": "amazontitan",
       "model": "amazon.nova-lite-v1:0"
   }
}</code></pre><h2>Création du pipeline d&#x27;analyse de l&#x27;examen</h2><p>Nous allons maintenant créer un pipeline de traitement qui utilisera le processeur d&#x27;inférence pour exécuter une invite d&#x27;analyse de révision. Cette invite enverra les données d&#x27;évaluation à Amazon Nova Lite, qui les exécutera :</p><ul><li>Classification du sentiment (positif, négatif ou neutre).</li><li>Résumé de l&#x27;examen.</li><li>Génération de mots-clés.</li><li>Mesure de l&#x27;authenticité (authentique | suspecte | générique).</li></ul><pre><code>PUT /_ingest/pipeline/review_analyzer_ai
{
      "processors": [
      {
        "script": 
            {
            "source": """ctx.prompt = "Analyze the following product review and return a structured JSON. Task: - Summarize the review concisely. - Detect and classify the sentiment as positive, neutral, or negative.- Generate relevant tags (keywords) based on the review content and detected sentiment. - Evaluate the authenticity of the review (authentic, suspicious, or generic). Review: " + ctx.review + " Respond in JSON format with the following fields: \"review_analyze\": {\"sentiment\": \"<positive | neutral | negative>\", \"authenticity\": \"<authentic | suspicious | generic>\",\"summary\": \"<short review summary>\", \"keywords\": [\"<keyword 1>\", \"<keyword 2>\", \"...\"]}}}"
            """
            }
      },
      {
        "inference": {
          "model_id": "bedrock_completion_amazon_nova-lite",
          "input_output": {
            "input_field": "prompt",
            "output_field": "result"
          }
        }
      },
      {
        "gsub": {
          "field": "result",
          "pattern": "```json",
          "replacement": ""
        } 
      },
      {
        "json" : {
          "field" : "result",
          "strict_json_parsing": false,
          "add_to_root" : true
        }
      },
      {
        "remove": {
          "field": "result"
        }
      },
      {
        "remove": {
          "field": "prompt"
        }
      }
    ]
}</code></pre><h2>Révision de l&#x27;indexation</h2><p>Nous indexons maintenant les avis sur les produits à l&#x27;aide de l&#x27;API Bulk. Le pipeline créé précédemment sera automatiquement appliqué, ajoutant l&#x27;analyse générée par le modèle Nova aux documents indexés.</p><pre><code>POST bulk/
{ "index": { "_index" : "products", "_id": 1, "pipeline":"review_analyzer_ai" } }
{ "product": "Pampers Pants Premium Care Fralda", "review": "Best diaper ever! Great material, lots of cotton, without all that plastic. Doesn't leak! My baby is a boy and every diaper leaked around the waist, this model solved the problem. Even on a small baby it's worth the effort of putting on the short diaper. I put it on my baby at 9 pm and only take it off in the morning, without any leaks." }
{ "index": { "_index" : "products", "_id": 2, "pipeline":"review_analyzer_ai" } }
{ "product": "Portable Electric Body Massager", "review": "It broke in three months for no apparent reason, thank goodness I didn't review it before. I don't recommend buying it because it has a short lifespan." }
{ "index": { "_index" : "products", "_id": 3, "pipeline":"review_analyzer_ai" } }
{ "product": "Havit Fuxi-H3 Black Quad-Mode Wired and Wireless Gaming Headset", "review": "The sound is good for the price, but the connectivity is horrible. You always need to be playing audio, otherwise it loses connection (I work from home, and this is very annoying). Sometimes it loses connection and you have to turn it off and on again to get it back on. The microphone is very sensitive, so it loses connection frequently and you have to turn the headset off and on for the microphone to work again. The flexibility of the stem is useless, because if you move it, the microphone can turn off. Sometimes I need to use Linux and the headset simply doesn't work. It's light and comfortable, the sound is adequate, but the connectivity is terrible." }
{ "index": { "_index" : "products", "_id": 4, "pipeline":"review_analyzer_ai" } }
{ "product": "Air Fryer 4L Oil Free Fryer Mondial", "review": "For those looking for value for money, it's a good option, but the tray (which is underneath the perforated basket) is already peeling a lot. My mother has one just like it and said that hers is even rusting, in other words, the material is MUCH inferior. There's also something that bothers me, because it looks like a microwave, it doesn't fry evenly, it's weaker in the middle and stronger on the sides. Buy at your own risk." }</code></pre><h2>Interrogation et analyse des résultats</h2><p>Enfin, nous lançons une requête pour voir comment le modèle Amazon Nova Lite analyse et classe les avis. En exécutant GET products/_search, nous obtenons les documents déjà enrichis des champs générés à partir du contenu de la revue.</p><p>Le modèle identifie le sentiment prédominant (positif, neutre ou négatif), génère des résumés concis, extrait les mots-clés pertinents et estime l&#x27;authenticité de chaque avis. Ces champs permettent de comprendre l&#x27;opinion du client sans avoir à lire le texte complet.</p><p>Pour interpréter les résultats, nous examinons</p><ul><li>Le sentiment, qui indique la perception globale du produit par le consommateur.</li><li>Le résumé, qui met en évidence les principaux points mentionnés.</li><li>Les mots-clés, qui peuvent être utilisés pour regrouper des avis similaires ou identifier des modèles de retour d&#x27;information.</li><li>L&#x27;authenticité, qui indique si l&#x27;avis semble digne de confiance. Cette fonction est utile pour la curation ou la modération.</li></ul><pre><code>   "hits": [
      {
        "_index": "products",
        "_id": "1",
        "_score": 1,
        "_ignored": [
          "review.keyword"
        ],
        "_source": {
          "product": "Pampers Pants Premium Care Fralda",
          "model_id": "bedrock_completion_amazon_nova-lite",
          "review_analyze": {
            "summary": "The reviewer praises the diaper for its great material, high cotton content, and leak-proof design, especially highlighting its effectiveness for their baby.",
            "sentiment": "positive",
            "keywords": [
              "best diaper",
              "great material",
              "cotton",
              "no plastic",
              "leak-proof",
              "baby",
              "effective"
            ],
            "authenticity": "authentic"
          },
          "review": "Best diaper ever! Great material, lots of cotton, without all that plastic. Doesn't leak! My baby is a boy and every diaper leaked around the waist, this model solved the problem. Even on a small baby it's worth the effort of putting on the short diaper. I put it on my baby at 9 pm and only take it off in the morning, without any leaks."
        }
      },
      {
        "_index": "products",
        "_id": "2",
        "_score": 1,
        "_source": {
          "product": "Portable Electric Body Massager",
          "model_id": "bedrock_completion_amazon_nova-lite",
          "review_analyze": {
            "summary": "The product broke in three months for no apparent reason and the reviewer does not recommend it due to its short lifespan.",
            "sentiment": "negative",
            "keywords": [
              "broke",
              "short lifespan",
              "not recommend"
            ],
            "authenticity": "authentic"
          },
          "review": "It broke in three months for no apparent reason, thank goodness I didn't review it before. I don't recommend buying it because it has a short lifespan."
        }
      },
      {
        "_index": "products",
        "_id": "3",
        "_score": 1,
        "_ignored": [
          "review.keyword"
        ],
        "_source": {
          "product": "Havit Fuxi-H3 Black Quad-Mode Wired and Wireless Gaming Headset",
          "model_id": "bedrock_completion_amazon_nova-lite",
          "review_analyze": {
            "summary": "The headset has good sound quality for the price but suffers from poor connectivity, especially when using the microphone or moving the headset. It also has compatibility issues with Linux.",
            "sentiment": "negative",
            "keywords": [
              "sound",
              "connectivity",
              "microphone",
              "compatibility",
              "annoying",
              "turn off and on",
              "Linux",
              "flexible stem",
              "work from home"
            ],
            "authenticity": "authentic"
          },
          "review": "The sound is good for the price, but the connectivity is horrible. You always need to be playing audio, otherwise it loses connection (I work from home, and this is very annoying). Sometimes it loses connection and you have to turn it off and on again to get it back on. The microphone is very sensitive, so it loses connection frequently and you have to turn the headset off and on for the microphone to work again. The flexibility of the stem is useless, because if you move it, the microphone can turn off. Sometimes I need to use Linux and the headset simply doesn't work. It's light and comfortable, the sound is adequate, but the connectivity is terrible."
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 1,
        "_ignored": [
          "review.keyword"
        ],
        "_source": {
          "product": "Air Fryer 4L Oil Free Fryer Mondial",
          "model_id": "bedrock_completion_amazon_nova-lite",
          "review_analyze": {
            "summary": "The product offers value for money but has issues with peeling, rusting, and uneven frying.",
            "sentiment": "negative",
            "keywords": [
              "value for money",
              "peeling",
              "rusting",
              "uneven frying",
              "weaker in the middle"
            ],
            "authenticity": "authentic"
          },
          "review": "For those looking for value for money, it's a good option, but the tray (which is underneath the perforated basket) is already peeling a lot. My mother has one just like it and said that hers is even rusting, in other words, the material is MUCH inferior. There's also something that bothers me, because it looks like a microwave, it doesn't fry evenly, it's weaker in the middle and stronger on the sides. Buy at your own risk."
        }
      }
    ]</code></pre><h2>Conclusions</h2><p>L&#x27;intégration entre Amazon Nova Lite et Elasticsearch a démontré comment les modèles de langage peuvent transformer des avis bruts en informations structurées et précieuses. En traitant les avis par le biais d&#x27;un pipeline, nous avons pu extraire le sentiment, l&#x27;authenticité, les résumés et les mots-clés de manière automatique et cohérente.</p><p>Les résultats montrent que le modèle peut comprendre le contexte des avis, classer les opinions des utilisateurs et mettre en évidence les points les plus pertinents de chaque expérience. Cela crée un ensemble de données beaucoup plus riche qui peut être exploité pour améliorer les capacités de recherche.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comment automatiser les synonymes et le téléchargement à l'aide de notre API Synonymes ?]]></title>
            <description><![CDATA[Découvrez comment les LLM peuvent être utilisés pour identifier et générer automatiquement des synonymes, permettant ainsi aux termes d'être chargés de manière programmatique dans l'API de synonymes d'Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/fr/blog/elasticsearch-synonyms-automate</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/elasticsearch-synonyms-automate</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
            <pubDate>Thu, 27 Mar 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/05a3cfeaa387561d5334ca3f1609035ddfff7481-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>L&#x27;amélioration de la qualité des résultats de recherche est essentielle pour offrir une expérience efficace aux utilisateurs. L&#x27;un des moyens d&#x27;optimiser les recherches consiste à étendre automatiquement les termes recherchés au moyen de synonymes. Cela permet d&#x27;interpréter les requêtes de manière plus large, de couvrir les variations linguistiques et donc d&#x27;améliorer la concordance des résultats.</p><p>Ce blog explore la manière dont les grands modèles de langage (LLM) peuvent être utilisés pour identifier et générer automatiquement des synonymes, ce qui permet de charger ces termes de manière programmatique dans l&#x27;API de synonymes d&#x27;Elasticsearch.</p><h2>Quand utiliser des synonymes ?</h2><p>L&#x27;utilisation de synonymes peut être une solution plus rapide et plus rentable que la recherche vectorielle. Sa mise en œuvre est plus simple car elle ne nécessite pas de connaissances approfondies en matière d&#x27;encastrements ni de processus complexe d&#x27;ingestion de vecteurs.</p><p>En outre, la consommation de ressources est plus faible, car la recherche vectorielle nécessite une plus grande capacité de stockage et de mémoire pour l&#x27;indexation de l&#x27;incorporation et la recherche.</p><p>Un autre aspect important est la régionalisation de la recherche. Grâce aux synonymes, il est possible d&#x27;adapter les termes à la langue et aux coutumes locales. Ceci est utile dans les situations où les embeddings peuvent ne pas correspondre à des expressions régionales ou à des termes spécifiques à un pays. Par exemple, certains mots ou acronymes peuvent avoir des significations différentes selon la région, mais sont naturellement traités comme des synonymes par les utilisateurs locaux. Au Brésil, cette situation est assez courante. &quot;Abacaxi&quot; et &quot;ananás&quot; sont le même fruit (ananas), mais le second terme est plus couramment utilisé dans certaines régions du nord-est. &quot;De même, le pão francês&quot;, bien connu dans le Sud-Est, peut être appelé &quot;pão careca&quot; dans le Nord-Est.</p><h2>Comment utiliser les LLM pour générer des synonymes ?</h2><p>Pour obtenir automatiquement des synonymes, on peut utiliser des LLM, qui analysent le contexte d&#x27;un terme et suggèrent des variations appropriées. Cette approche permet d&#x27;étendre dynamiquement les synonymes, ce qui garantit une recherche plus large et plus précise sans dépendre d&#x27;un dictionnaire fixe.</p><p>Dans cette démonstration, nous utiliserons un LLM pour générer des synonymes pour des produits de commerce électronique. De nombreuses recherches ne donnent que peu ou pas de résultats en raison de variations dans les termes recherchés. Les synonymes permettent de résoudre ce problème. Par exemple, une recherche sur &quot;smartphone&quot; peut englober différents modèles de téléphones mobiles, ce qui permet aux utilisateurs de trouver les produits qu&#x27;ils recherchent.</p><h3>Produits requis</h3><p>Avant de commencer, nous devons configurer l&#x27;environnement et définir les dépendances nécessaires. Nous utiliserons la solution fournie par Elastic pour <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">exécuter Elasticsearch et Kibana localement dans Docker</a>. Le code sera écrit en Python, v3.9.6, avec les dépendances suivantes :</p><pre><code>pip install openai==1.59.8 elasticsearch==8.15.1</code></pre><h3>Création de l&#x27;index des produits</h3><p>Dans un premier temps, nous créerons un index des produits sans support de synonymes. Cela nous permettra de valider les requêtes et de les comparer à un index comprenant des synonymes.</p><p>Pour créer l&#x27;index, nous chargeons en masse un jeu de données de produits à l&#x27;aide de la commande suivante dans Kibana DevTools :</p><pre><code>POST _bulk
{"index": {"_index": "products", "_id": 10001}}
{"category": "Electronics", "name": "iPhone 14 Pro"}
{"index": {"_index": "products", "_id": 10007}}
{"category": "Electronics", "name": "MacBook Pro 16-inch"}
{"index": {"_index": "products", "_id": 10013}}
{"category": "Electronics", "name": "Samsung Galaxy Tab S8"}
{"index": {"_index": "products", "_id": 10037}}
{"category": "Electronics", "name": "Apple Watch Series 8"}
{"index": {"_index": "products", "_id": 10049}}
{"category": "Electronics", "name": "Kindle Paperwhite"}
{"index": {"_index": "products", "_id": 10067}}
{"category": "Electronics", "name": "Samsung QLED 4K TV"}
{"index": {"_index": "products", "_id": 10073}}
{"category": "Electronics", "name": "HP Spectre x360 Laptop"}
{"index": {"_index": "products", "_id": 10079}}
{"category": "Electronics", "name": "Apple AirPods Pro"}
{"index": {"_index": "products", "_id": 10115}}
{"category": "Electronics", "name": "Amazon Echo Show 10"}
{"index": {"_index": "products", "_id": 10121}}
{"category": "Electronics", "name": "Apple iPad Air"}
{"index": {"_index": "products", "_id": 10127}}
{"category": "Electronics", "name": "Apple AirPods Max"}
{"index": {"_index": "products", "_id": 10151}}
{"category": "Electronics", "name": "Sony WH-1000XM4 Headphones"}
{"index": {"_index": "products", "_id": 10157}}
{"category": "Electronics", "name": "Google Pixel 6 Pro"}
{"index": {"_index": "products", "_id": 10163}}
{"category": "Electronics", "name": "Apple MacBook Air"}
{"index": {"_index": "products", "_id": 10181}}
{"category": "Electronics", "name": "Google Pixelbook Go"}
{"index": {"_index": "products", "_id": 10187}}
{"category": "Electronics", "name": "Sonos Beam Soundbar"}
{"index": {"_index": "products", "_id": 10199}}
{"category": "Electronics", "name": "Apple TV 4K"}
{"index": {"_index": "products", "_id": 10205}}
{"category": "Electronics", "name": "Samsung Galaxy Watch 4"}
{"index": {"_index": "products", "_id": 10211}}
{"category": "Electronics", "name": "Apple MacBook Pro 16-inch"}
{"index": {"_index": "products", "_id": 10223}}
{"category": "Electronics", "name": "Amazon Echo Dot (4th Gen)"}</code></pre><h3>Générer des synonymes avec LLM</h3><p>Dans cette étape, nous utiliserons un LLM pour générer dynamiquement des synonymes. Pour ce faire, nous intégrerons l&#x27;API OpenAI, en définissant un modèle et une invite appropriés. Le LLM recevra la catégorie et le nom du produit, en veillant à ce que les synonymes soient pertinents du point de vue contextuel.</p><pre><code>import json
import logging

from openai import OpenAI

def call_gpt(prompt, model):
    try:
        logging.info("generate synonyms by llm...")
        response = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7,
            max_tokens=1000
        )
        content = response.choices[0].message.content.strip()
        return content
    except Exception as e:
        logging.error(f"Failed to use model: {e}")
        return None

def generate_synonyms(category, products):
   synonyms = {}

   for product in products:
       prompt = f"You are an expert in generating synonyms for products. Based on the category and product name provided, generate synonyms or related terms. Follow these rules:\n"
       prompt += "1. **Format**: The first word should be the main item (part of the product name, excluding the brand), followed by up to 3 synonyms separated by commas.\n"
       prompt += "2. **Exclude the brand**: Do not include the brand name in the synonyms.\n"
       prompt += "3. **Maximum synonyms**: Generate a maximum of 3 synonyms per product.\n\n"
       prompt += f"The category is: **{category}**, and the product is: **{product}**. Return only the synonyms in the requested format, without additional explanations."

       response = call_gpt(prompt, "gpt-4o")
       synonyms[product] = response

   return synonyms</code></pre><p>À partir de l&#x27;index des produits créé, nous récupérerons tous les articles de la catégorie &quot;Electronics&quot; et enverrons leurs noms au LLM. Le résultat attendu sera quelque chose comme :</p><pre><code>{
  "iPhone 14 Pro": ["iPhone", "smartphone", "mobile", "handset"],
  "MacBook Pro 16-inch": ["MacBook", "Laptop", "Notebook", "Ultrabook"],
  "Samsung Galaxy Tab S8": ["Tab", "Tablet", "Slate", "Pad"],
  "Bose QuietComfort 35 Headphones": ["Headphones", "earphones", "earbuds", "headset"]
}</code></pre><p>Avec les synonymes générés, nous pouvons les enregistrer dans Elasticsearch à l&#x27;aide de l&#x27;API Synonyms.</p><h3>Gestion des synonymes avec l&#x27;API Synonyms</h3><p>L&#x27;API Synonymes offre un moyen efficace de gérer les ensembles de synonymes directement dans le système. Chaque ensemble de synonymes se compose de règles de synonymie, selon lesquelles un groupe de mots est traité comme équivalent dans les recherches.</p><p><strong>Exemple de création d&#x27;un jeu de synonymes</strong></p><pre><code>PUT _synonyms/my-synonyms-set
{
  "synonyms_set": [
    {
      "id": "rule-1",
      "synonyms": "hello, hi"
    },
    {
      "synonyms": "bye, goodbye"
    }
  ]
}</code></pre><p><br/>Cela crée un ensemble appelé &quot;my-synonyms-set,&quot; où &quot;hello&quot; et &quot;hi&quot; sont traités comme des équivalents, ainsi que &quot;bye&quot; et &quot;goodbye.&quot;</p><h2>Mise en œuvre de la création de synonymes pour le catalogue de produits</h2><p>Vous trouverez ci-dessous la méthode permettant de construire un ensemble de synonymes et de l&#x27;insérer dans Elasticsearch. Les règles de synonymie sont générées sur la base du mappage des synonymes suggérés par le LLM. Chaque règle possède un identifiant, correspondant au nom du produit au format &quot;slug&quot;, et la liste des synonymes calculée par le LLM.</p><pre><code>import json
import logging

from elasticsearch import Elasticsearch
from slugify import slugify

es = Elasticsearch(
    "http://localhost:9200",
    api_key="your_api_key"
)

def mount_synonyms(results):
   synonyms_set = [{"id": slugify(product), "synonyms": synonyms} for product, synonyms in
                   results.items()]

   try:
       response = es.synonyms.put_synonym(id="products-synonyms-set",
                                                 synonyms_set=synonyms_set)

       logging.info(json.dumps(response.body, indent=4))
       return response.body
   except Exception as e:
       logging.error(f"Error create synonyms: {str(e)}")
       return None</code></pre><p>Vous trouverez ci-dessous la demande de création d&#x27;un ensemble de synonymes :</p><pre><code>{
   "synonyms_set":[
      {
         "id": "iphone-14-pro",
         "synonyms": "iPhone, smartphone, mobile, handset"
      },
      {
         "id": "macbook-pro-16-inch",
         "synonyms": "MacBook, Laptop, Notebook, Computer"
      },
      {
         "id": "samsung-galaxy-tab-s8",
         "synonyms": "Tablet, Slate, Pad, Device"
      },
      {
         "id": "garmin-forerunner-945",
         "synonyms": "Forerunner, smartwatch, fitness watch, GPS watch"
      },
      {
         "id": "bose-quietcomfort-35-headphones",
         "synonyms": "Headphones, Earphones, Headset, Cans"
      }
   ]
}</code></pre><p>Une fois l&#x27;ensemble de synonymes créé dans le cluster, nous pouvons passer à l&#x27;étape suivante, qui consiste à créer un nouvel index avec prise en charge des synonymes à l&#x27;aide de l&#x27;ensemble défini.</p><p>Le code Python complet avec les synonymes générés par LLM et la création d&#x27;ensembles de synonymes définie par l&#x27;API Synonyms est ci-dessous :</p><pre><code>import json
import logging

from elasticsearch import Elasticsearch
from openai import OpenAI
from slugify import slugify

logging.basicConfig(level=logging.INFO)

client = OpenAI(
   api_key="your-key",
)

es = Elasticsearch(
    "http://localhost:9200",
    api_key="your_api_key"
)


def call_gpt(prompt, model):
   try:
       logging.info("generate synonyms by llm...")
       response = client.chat.completions.create(
           model=model,
           messages=[{"role": "user", "content": prompt}],
           temperature=0.7,
           max_tokens=1000
       )
       content = response.choices[0].message.content.strip()
       return content
   except Exception as e:
       logging.error(f"Failed to use model: {e}")
       return None


def generate_synonyms(category, products):
   synonyms = {}

   for product in products:
       prompt = f"You are an expert in generating synonyms for products. Based on the category and product name provided, generate synonyms or related terms. Follow these rules:\n"
       prompt += "1. **Format**: The first word should be the main item (part of the product name, excluding the brand), followed by up to 3 synonyms separated by commas.\n"
       prompt += "2. **Exclude the brand**: Do not include the brand name in the synonyms.\n"
       prompt += "3. **Maximum synonyms**: Generate a maximum of 3 synonyms per product.\n\n"
       prompt += f"The category is: **{category}**, and the product is: **{product}**. Return only the synonyms in the requested format, without additional explanations."

       response = call_gpt(prompt, "gpt-4o")
       synonyms[product] = response

   return synonyms


def get_products(category):
   query = {
       "size": 50,
       "_source": ["name"],
       "query": {
           "bool": {
               "filter": [
                   {
                       "term": {
                           "category.keyword": category
                       }
                   }
               ]
           }
       }
   }
   response = es.search(index="products", body=query)

   if response["hits"]["total"]["value"] > 0:
       product_names = [hit["_source"]["name"] for hit in response["hits"]["hits"]]
       return product_names
   else:
       return []


def mount_synonyms(results):
   synonyms_set = [{"id": slugify(product), "synonyms": synonyms} for product, synonyms in
                   results.items()]

   try:
       es_client = get_client_es()
       response = es_client.synonyms.put_synonym(id="products-synonyms-set",
                                                 synonyms_set=synonyms_set)

       logging.info(json.dumps(response.body, indent=4))
       return response.body
   except Exception as e:
       logging.error(f"Erro update synonyms: {str(e)}")
       return None


if __name__ == '__main__':
   category = "Electronics"
   products = get_products("Electronics")
   llm_synonyms = generate_synonyms(category, products)
   mount_synonyms(llm_synonyms)</code></pre><h3>Création d&#x27;un index avec prise en charge des synonymes</h3><p>Un nouvel index sera créé dans lequel toutes les données de l&#x27;index <code>products</code> seront réindexées. Cet index utilisera le <code>synonyms_filter</code>, qui applique le <code>products-synonyms-set</code> créé précédemment.</p><p>Vous trouverez ci-dessous le mappage de l&#x27;index configuré pour utiliser les synonymes :</p><pre><code>PUT products_02
{
  "settings": {
    "analysis": {
      "filter": {
        "synonyms_filter": {
          "type": "synonym",
          "synonyms_set": "products-synonyms-set",
          "updateable": true
        }
      },
      "analyzer": {
        "synonyms_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "synonyms_filter"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "ID": {
        "type": "long"
      },
      "category": {
        "type": "keyword"
      },
      "name": {
        "type": "text",
        "analyzer": "standard",
        "search_analyzer": "synonyms_analyzer"
      }
    }
  }
}</code></pre><h3>Réindexation de l&#x27;index <code>products</code></h3><p>Nous allons maintenant utiliser l&#x27;<strong>API Reindex</strong> pour migrer les données de l&#x27;index <code>products</code> vers le nouvel index <code>products_02</code>, qui prend en charge les synonymes. Le code suivant a été exécuté dans Kibana DevTools :<br/></p><pre><code>POST _reindex
{
  "source": {
    "index": "products"
  },
  "dest": {
    "index": "products_02"
  }
}</code></pre><p>Après la migration, l&#x27;index <code>products_02</code> sera alimenté et prêt à valider les recherches à l&#x27;aide du jeu de synonymes configuré.</p><h3>Valider la recherche avec des synonymes</h3><p>Comparons les résultats de recherche entre les deux index. Nous allons exécuter la même requête sur les deux index et vérifier si les synonymes sont utilisés pour récupérer les résultats.</p><h4>Recherche dans l&#x27;index <code>products</code> (sans synonymes)</h4><p>Nous utiliserons Kibana pour effectuer des recherches et analyser les résultats. Dans le menu Analytics &gt; Discovery, nous allons créer une vue de données pour visualiser les données des index que nous avons créés.</p><p>Dans Discovery, cliquez sur Data View et définissez un nom et un modèle d&#x27;index. Pour l&#x27;index &quot;<strong>products</strong>&quot;, nous utiliserons le modèle &quot;<strong>products</strong>&quot;. Ensuite, nous répéterons le processus pour créer une nouvelle vue de données pour l&#x27;index &quot;<strong>products_02</strong>&quot;, en utilisant le modèle &quot;<strong>products_02&quot;.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3ad4a6891a3905e96532a312932fdf3a8216aec2-1600x599.png" style="width: 100%;" /><p>Une fois les vues de données configurées, nous pouvons retourner sur Analytics &gt; Discovery et commencer les validations.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/422c4b2b51abae6580cad25085d1b8a365fc6b9e-1294x850.png" style="width: 100%;" /><p>Ici, après avoir sélectionné les produits DataView et effectué une recherche pour le terme &quot;tablet&quot;, nous n&#x27;obtenons aucun résultat, même si nous savons qu&#x27;il existe des produits tels que &quot;Kindle Paperwhite&quot; et &quot;Apple iPad Air&quot;.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e4ae3a785fdd93f48d7c7d204185ded149126f2c-1600x862.png" style="width: 100%;" /><h4>Recherche dans l&#x27;index <code>products_02</code> (support des synonymes)</h4><p>Lors de l&#x27;exécution de la même requête sur la vue de données &quot;<strong>products_synonyms</strong>&quot;, qui prend en charge les synonymes, les produits ont été récupérés avec succès. Cela prouve que le jeu de synonymes configuré fonctionne correctement, en garantissant que les différentes variations des termes recherchés renvoient les résultats escomptés.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e609749c39e90d5c82fa846af6124679dd62bcb8-1600x526.png" style="width: 100%;" /><p>Nous pouvons obtenir le même résultat en exécutant la même requête directement dans Kibana DevTools. Il suffit d&#x27;effectuer une recherche dans l&#x27;index products_02 à l&#x27;aide de l&#x27;API de recherche Elasticsearch :</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/504d0d1f96dcfbceb309063dc0716bcee64ad2f8-1600x870.png" style="width: 100%;" /><h2>Conclusion</h2><p>La mise en œuvre de synonymes dans Elasticsearch a permis d&#x27;améliorer la précision et la couverture des recherches dans les catalogues de produits. Le principal facteur de différenciation était l&#x27;utilisation d&#x27;un <strong>LLM</strong>, qui générait des synonymes automatiquement et en fonction du contexte, éliminant ainsi le besoin de listes prédéfinies. Le modèle a analysé les noms et les catégories de produits, en veillant à ce que les synonymes soient pertinents pour le commerce électronique.</p><p>En outre, l&#x27;<strong>API Synonymes</strong> a simplifié la gestion des dictionnaires, en permettant de modifier dynamiquement les ensembles de synonymes. Grâce à cette approche, la recherche est devenue plus souple et plus adaptable aux différents types de requêtes des utilisateurs.</p><p>Ce processus peut être continuellement amélioré grâce à de nouvelles données et à des ajustements de modèles, ce qui garantit une expérience de recherche de plus en plus efficace.</p><h2>Références</h2><p><strong>Exécuter Elasticsearch localement</strong></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html</a></p><p><strong>Synonymes API</strong></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/synonyms-apis.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/synonyms-apis.html</a></p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/late-interaction-model-colpali-scale</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/late-interaction-model-colpali-scale</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Peter Straßer, Benjamin Trent]]></dc:creator>
            <pubDate>Thu, 20 Mar 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c780b78a07573f2df1cfef8b29a7109f839b0ab3-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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&#x27;interaction tardive présente alors deux défis :</p><ol><li>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..</li><li>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.</li></ol><p>Examinons quelques techniques pour remédier à ces problèmes.</p><h2>Techniques pour optimiser les modèles d&#x27;interactions tardives</h2><h3>Vecteurs de bits</h3><p>Afin de réduire l&#x27;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><pre><code>def to_bit_vectors(embeddings: list) -> list:
    return [
        np.packbits(np.where(np.array(embedding) > 0, 1, 0))
        .astype(np.int8)
        .tobytes()
        .hex()
        for embedding in embeddings
    ]</code></pre><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><pre><code>mappings = {
    "mappings": {
        "properties": {
            "col_pali_vectors": {
                "type": "rank_vectors",
                "element_type": "bit"
            }
        }
    }
}

es.indices.create(index=INDEX_NAME, body=mappings)</code></pre><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><pre><code>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
}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/51b989446e4099745971e1eac27d147a78d13e0a-1600x480.png" style="width: 100%;" /><p>En échange d&#x27;un peu de précision, cela nous permet d&#x27;utiliser la distance de hamming (<code>maxSimInvHamming(...)</code>), qui est capable de tirer parti d&#x27;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&#x27;aide du vecteur d&#x27;interaction tardive en pleine fidélité :</p><pre><code>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
}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/89f3c795c6dc44b2f7d46801320288316b47e1b2-1600x488.png" style="width: 100%;" /><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/366a70a23e37d403788327b7aefc873fd4482f5e-1235x86.png" style="width: 100%;" /><p>Et un vecteur de requête <em>Q :</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/978ec31e0afbc0eaebf548a015b628c0cad84625-1247x84.png" style="width: 100%;" /><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&#x27;inverse de cette distance de hamming. Rappelez-vous : plus les vecteurs sont similaires, plus la distance de Hamming est petite. L&#x27;inverser permet aux vecteurs les plus similaires d&#x27;ê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&#x27;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&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/508e8498ba969534d2a0131d431c75735fc27cb9-1399x611.png" style="width: 100%;" /><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&#x27;utilisation de vecteurs de bits nous permet d&#x27;économiser de manière significative de l&#x27;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&#x27;index HNSW d&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7c3b4dffb70bd95f67deb35f8c01e73c5286c2ab-1476x1102.png" style="width: 100%;" /><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&#x27;ingestion, mais ici nous utiliserons une simple fonction Python :</p><pre><code>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 > 0:
        normalized_avg_vector = avg_vector / norm
    else:
        normalized_avg_vector = avg_vector

    return normalized_avg_vector.tolist()</code></pre><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><pre><code>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)</code></pre><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&#x27;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&#x27;énormes ensembles de données qui ne seraient autrement pas possibles.</p><p>À présent, nous recherchons simplement à l&#x27;aide de la requête knn pour trouver nos documents les plus pertinents.</p><pre><code>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
}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a500f9907020f032c3b1c7a48ed8c759dc2a1dd4-1600x498.png" style="width: 100%;" /><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&#x27;interaction tardive de ColPali. </p><pre><code>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
}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/797f490860af87fcf29f34668a5c4511419c6fd0-1600x501.png" style="width: 100%;" /><p>Ici, nous utilisons l&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/09eae0b768f4b450e555d7e53e57561bd52de2c0-1412x1056.png" style="width: 100%;" /><p>Le pooling de jetons fonctionne en regroupant des embeddings de jetons similaires au sein d&#x27;un document en clusters à l&#x27;aide d&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/343cc9eaf54af8c7a125d4115838f3c7d5659de0-1600x1007.png" style="width: 100%;" /><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><pre><code>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) -> list:
    tensor = torch.tensor(embedding).unsqueeze(0)
    pooled = pooler.pool_embeddings(tensor)
    return pooled.squeeze(0).tolist()</code></pre><p>Nous avons maintenant un vecteur dont les dimensions ont été réduites d’environ 66,7 %. Nous l&#x27;indexons comme d&#x27;habitude et nous pouvons rechercher dessus avec notre fonction <code>maxSimDotProduct()</code>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2412c5db7d79a590b96d42fe01f140c42f010612-1600x481.png" style="width: 100%;" /><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&#x27;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&#x27;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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/75e1fc9e601aa7a6e88f137565c1919166c7a71c-1480x458.png" style="width: 100%;" /><p>Pour cette raison, nous recommandons d&#x27;utiliser les modèles d&#x27;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&#x27;en est-il de l&#x27;encodeur croisé ? Les modèles d&#x27;interaction tardive sont-ils meilleurs parce qu&#x27;ils sont moins chers à exécuter au moment de la requête ? Comme c&#x27;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&#x27;ils ne nécessitent aucune indexation de vecteurs et peuvent fonctionner de manière apatride. Résultats obtenus :</p><ul><li>Moins d&#x27;espace disque utilisé</li><li>Un système plus simple</li><li>Qualité supérieure des résultats de recherche</li><li>Latence plus élevée, et donc impossible à repositionner aussi profondément</li></ul><p>D&#x27;autre part, les modèles d&#x27;interaction tardive peuvent décharger une partie de ce calcul sur l&#x27;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&#x27;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&#x27;utilisation d&#x27;un modèle d&#x27;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&#x27;utilisation et à expérimenter les différents outils qu&#x27;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&#x27;interaction tardive comme ColPali pour la recherche vectorielle à grande échelle dans Elasticsearch. Bien que les modèles d&#x27;interaction tardive offrent un bon équilibre entre l&#x27;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>Des <strong>vecteurs de bits</strong> pour réduire de manière significative l&#x27;espace disque tout en tirant parti de calculs de similarité efficaces, tels que la distance de Hamming ou la similarité maximale asymétrique.</li><li>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&#x27;indexation HNSW.</li><li>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.</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&#x27;efficacité de stockage, ces outils et techniques vous permettent d&#x27;équilibrer les performances et la qualité selon vos besoins pour vos applications du monde réel.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/fr/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/fr/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Hegarty, Hemant Malik]]></dc:creator>
            <pubDate>Wed, 19 Mar 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/38bc0377a6adce7eae0099f61902fdbbe644eb4a-1440x960.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Depuis un certain temps, l&#x27;équipe d&#x27;Elastic Engineering s&#x27;efforce d&#x27;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&#x27;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&#x27;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&#x27;approche algorithmique de BBQ pour en tirer encore plus d&#x27;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&#x27;accélération avec ces curieuses puces informatiques, dont vous avez peut-être entendu parler - les GPU NVIDIA ! (Sérieusement, vous n&#x27;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&#x27;exploiter tous les avantages disponibles lorsque vous disposez de GPU.</p><p>Dans ce billet, nous nous penchons sur notre collaboration avec l&#x27;é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&#x27;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&#x27;annoncer que l&#x27;équipe d&#x27;ingénieurs d&#x27;Elasticsearch participe à l&#x27;élaboration de l&#x27;API Java cuVS open-source pour les développeurs, qui expose des bindings pour les algorithmes de recherche vectorielle. Ce travail s&#x27;appuie sur notre expérience antérieure en matière de FFI au Panama. Elasticsearch et Apache Lucene utilisent l&#x27;API NVIDIA cuVS pour construire le graphe pendant l&#x27;indexation. D&#x27;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&#x27;accélération GPU à la recherche vectorielle en fournissant un débit plus élevé, une latence plus faible et des temps de construction d&#x27;index plus rapides. Mais Elasticsearch et Apache Lucene sont écrits en Java ; comment cela fonctionnera-t-il ?</p><p>C&#x27;est là qu&#x27;interviennent <a href="https://github.com/SearchScale/lucene-cuvs">lucene-cuvs</a> et la collaboration Elastic-NVIDIA-SearchScale pour l&#x27;intégrer à l&#x27;écosystème Lucene afin d&#x27;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&#x27;é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&#x27;est génial ! Mais... il y a toujours un &quot;mais&quot;, n&#x27;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&#x27;API cuVS en Java 21 et en encapsulant l&#x27;implémentation dans un jar multi-versions ciblant Java 22. Cela permet d&#x27;utiliser cuVS Java directement dans Lucene et Elasticsearch.</p><p>Ok, maintenant que nous avons l&#x27;API Java cuVS, de quoi d&#x27;autre avons-nous besoin ?</p><h2>Deux algorithmes pour l&#x27;unité centrale</h2><p>Elasticsearch prend en charge l&#x27;<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 [<strong></strong><em>CUDA</em></a> <a href="https://arxiv.org/pdf/2308.15136"><strong></strong><em>ANN</em></a> <a href="https://arxiv.org/pdf/2308.15136"><strong></strong><em></em></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&#x27;ajouter la prise en charge de CAGRA, examinons comment Elasticsearch et Lucene accèdent aux données d&#x27;index par le biais d&#x27;un &quot;format de codec&quot;. Il s&#x27;agit de</p><ol><li>la représentation sur disque,</li><li>les interfaces de lecture et d&#x27;écriture des données,</li><li>et les mécanismes permettant de gérer l&#x27;architecture à base de segments de Lucene.</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&#x27;API Java cuVS pour l&#x27;indexation et la recherche sur le GPU. À partir de là, nous &quot;plongeons&quot; ce type de codec dans les correspondances d&#x27;Elasticsearch avec un type de champ dans l&#x27;index. Par conséquent, vos requêtes KNN existantes continuent de fonctionner, que l&#x27;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&#x27;aborder dans un prochain blog. Voici l&#x27;architecture de haut niveau d&#x27;un Elasticsearch accéléré par le GPU.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/be6b7356c03df4dee7230625c2c9af3b019f93be-756x510.png" style="width: 100%;" /><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&#x27;unité centrale.</p><h2>Indexation et recherche sur le GPU : Prendre quelques décisions &quot;fondamentales</h2><p>Avec l&#x27;<a href="https://www.elastic.co/fr/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">architecture</a> sans état d&#x27;Elasticsearch Serverless, qui sépare l&#x27;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>Indexation et recherche sur le GPU : Pendant l&#x27;indexation, construire un graphe CAGRA et l&#x27;utiliser pendant la recherche - idéal lorsqu&#x27;une recherche à très faible latence est requise.</li><li>Indexation sur GPU et recherche sur CPU : Pendant l&#x27;indexation, construire un graphe CAGRA et le convertir en graphe HNSW. Le graphique HNSW est stocké dans l&#x27;index, qui peut ensuite être utilisé par l&#x27;unité centrale pour effectuer des recherches.</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&#x27;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&#x27;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&#x27;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&#x27;analyse des performances. Accrochez-vous à vos chapeaux de curiosité 🎩!</p>]]></content:encoded>
        </item>
    </channel>
</rss>