<?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[Java - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Java - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/fr/search-labs/blog/category/java-programming</link>
    </image>
    <link>https://www.elastic.co/fr/search-labs/blog/category/java-programming</link>
    <atom:link href="https://www.elastic.co/fr/search-labs/rss/category/java-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[fr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 15:07:24 GMT</lastBuildDate>
  <item>
    <title><![CDATA[LangChain4j avec Elasticsearch comme magasin de plongements]]></title>
    <description><![CDATA[LangChain4j (LangChain pour Java) a Elasticsearch comme magasin intégré. Découvrez comment l'utiliser pour construire votre application RAG en Java simple.]]></description>
    <content:encoded><![CDATA[<p>
Dans l'<a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">article précédent</a>, nous avons découvert ce qu'est LangChain4j et comment l'utiliser :</p><ul><li><p>Discutez avec les LLM en mettant en place un <code>ChatLanguageModel</code> et un <code>ChatMemory</code></p></li><li><p>Conserver l'historique du chat en mémoire pour se rappeler le contexte d'une discussion précédente avec un LLM</p></li></ul><p>Cet article de blog traite de la manière de procéder :</p><ul><li><p>Création d'encastrements vectoriels à partir d'exemples de textes</p></li><li><p>Stocker les embeddings vectoriels dans le magasin d'embedding d'Elasticsearch </p></li><li><p>Recherche de vecteurs similaires</p></li></ul><h2>Créer des embeddings</h2><p>Pour créer des embeddings, nous devons définir un <code>EmbeddingModel</code> à utiliser. Par exemple, nous pouvons utiliser le même modèle de mistral que celui utilisé dans le <a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">billet précédent.</a> Il s'agissait de courir avec l'ollama :</p>EmbeddingModel model = OllamaEmbeddingModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();<p>Un modèle est capable de générer des vecteurs à partir d'un texte. Nous pouvons ici vérifier le nombre de dimensions générées par le modèle :</p>Logger.info("Embedding model has {} dimensions.", model.dimension());
// This gives: Embedding model has 4096 dimensions.<p>Pour générer des vecteurs à partir d'un texte, nous pouvons utiliser :</p>Response&lt;Embedding&gt; response = model.embed("A text here");<p>Si nous voulons également fournir des métadonnées pour nous permettre de filtrer des éléments tels que le texte, le prix, la date de sortie ou autre, nous pouvons utiliser <code>Metadata.from()</code>. Par exemple, nous ajoutons ici le nom du jeu comme champ de métadonnées :</p>TextSegment game1 = TextSegment.from("""
    The game starts off with the main character Guybrush Threepwood stating "I want to be a pirate!"
    To do so, he must prove himself to three old pirate captains. During the perilous pirate trials, 
    he meets the beautiful governor Elaine Marley, with whom he falls in love, unaware that the ghost pirate 
    LeChuck also has his eyes on her. When Elaine is kidnapped, Guybrush procures crew and ship to track 
    LeChuck down, defeat him and rescue his love.
""", Metadata.from("gameName", "The Secret of Monkey Island"));
Response&lt;Embedding&gt; response1 = model.embed(game1);
TextSegment game2 = TextSegment.from("""
    Out Run is a pseudo-3D driving video game in which the player controls a Ferrari Testarossa 
    convertible from a third-person rear perspective. The camera is placed near the ground, simulating 
    a Ferrari driver's position and limiting the player's view into the distance. The road curves, 
    crests, and dips, which increases the challenge by obscuring upcoming obstacles such as traffic 
    that the player must avoid. The object of the game is to reach the finish line against a timer.
    The game world is divided into multiple stages that each end in a checkpoint, and reaching the end 
    of a stage provides more time. Near the end of each stage, the track forks to give the player a 
    choice of routes leading to five final destinations. The destinations represent different 
    difficulty levels and each conclude with their own ending scene, among them the Ferrari breaking 
    down or being presented a trophy.
""", Metadata.from("gameName", "Out Run"));
Response&lt;Embedding&gt; response2 = model.embed(game2);<p>Si vous souhaitez exécuter ce code, veuillez consulter la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step5EmbedddingsTest.java">Step5EmbedddingsTest.java</a>.</p><h2>Ajouter Elasticsearch pour stocker nos vecteurs</h2><p>LangChain4j fournit un magasin d'intégration en mémoire. Cette fonction est utile pour exécuter des tests simples :</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore = new InMemoryEmbeddingStore&lt;&gt;();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Mais il est évident que cela ne pourrait pas fonctionner avec un ensemble de données beaucoup plus important car ce datastore stocke tout en mémoire et nous ne disposons pas d'une mémoire infinie sur nos serveurs. Ainsi, nous pourrions plutôt stocker nos enregistrements dans Elasticsearch, qui est par définition "élastique" et peut évoluer avec vos données. Pour cela, ajoutons Elasticsearch à notre projet :</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;

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

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

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

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

// Check the cluster is running
client.performRequest(new Request("GET", "/"));<p>Pour utiliser Elasticsearch comme magasin d'intégration, il suffit de "" passer du datastore LangChain4j en mémoire au datastore Elasticsearch :</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Cela permettra de stocker vos vecteurs dans Elasticsearch dans un index <code>default</code>. Vous pouvez également changer le nom de l'index en quelque chose de plus significatif :</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .indexName("games")
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Si vous souhaitez exécuter ce code, veuillez consulter la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step6ElasticsearchEmbedddingsTest.java">Step6ElasticsearchEmbedddingsTest.java</a>.</p><h2>Recherche de vecteurs similaires</h2><p>Pour rechercher des vecteurs similaires, nous devons d'abord transformer notre question en une représentation vectorielle en utilisant le même modèle que celui utilisé précédemment. Nous l'avons déjà fait, il n'est donc pas difficile de le faire à nouveau. Notez que nous n'avons pas besoin des métadonnées dans ce cas :</p>String question = "I want to pilot a car";
Embedding questionAsVector = model.embed(question).content();<p>Nous pouvons construire une requête de recherche avec cette représentation de notre question et demander au magasin d'intégration de trouver les premiers vecteurs :</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Nous pouvons maintenant itérer sur les résultats et imprimer certaines informations, comme le nom du jeu qui provient des métadonnées et le score :</p>result.matches().forEach(m -&gt; Logger.info("{} - score [{}]",
  m.embedded().metadata().getString("gameName"), m.score()));<p>Comme on pouvait s'y attendre, cela nous donne "Out Run" comme premier résultat :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ca0dcfdb1a9c94f/6a170291cf4f256938b2d017/140b6a962e5edbb4870419250e30bfb815b0d73e-640x480.gif" alt="Sortie de route" />Out Run - score [0.86672974]
The Secret of Monkey Island - score [0.85569763]<p>Si vous souhaitez exécuter ce code, consultez la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L110-L129">Step7SearchForVectorsTest.java</a>. </p><h2>L'envers du décor</h2><p>La configuration par défaut du magasin Elasticsearch Embedding utilise la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-knn-query.html">requête kNN approximative</a> en arrière-plan.</p>POST games/_search
{
  "query" : {
    "knn": {
      "field": "vector",
      "query_vector": [-0.019137882, /* ... */, -0.0148779955]
    }
  }
}<p>Mais cela peut être modifié en fournissant une autre configuration (<code>ElasticsearchConfigurationScript</code>) que celle par défaut (<code>ElasticsearchConfigurationKnn</code>) au magasin d'intégration :</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .configuration(ElasticsearchConfigurationScript.builder().build())
    .indexName("games")
    .restClient(client)
    .build();<p>L'implémentation de <code>ElasticsearchConfigurationScript</code> exécute en coulisse une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html">requête</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"><code>script_score</code></a> à l'aide d'une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine">fonction</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"><code>cosineSimilarity</code></a>.</p><p>En principe, lors d'un appel :</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Il s'agit maintenant d'un appel :</p>POST games/_search
{
  "query": {
    "script_score": {
      "script": {
        "source": "(cosineSimilarity(params.query_vector, 'vector') + 1.0) / 2",
        "params": {
          "queryVector": [-0.019137882, /* ... */, -0.0148779955]
        }
      }
    }
  }
}<p>Dans ce cas, le résultat ne change pas en termes d'ordre "" mais le score est simplement ajusté parce que l'appel <code>cosineSimilarity</code> n'utilise pas d'approximation mais calcule le cosinus pour chacun des vecteurs correspondants :</p>Out Run - score [0.871952]
The Secret of Monkey Island - score [0.86380446]<p>Si vous souhaitez exécuter ce code, consultez la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L132-L155">Step7SearchForVectorsTest.java</a>.</p><h2>Conclusion</h2><p>Nous avons vu comment vous pouvez facilement générer des embeddings à partir de votre texte et comment vous pouvez stocker et rechercher les voisins les plus proches dans Elasticsearch en utilisant deux approches différentes :</p><ul><li><p>Utilisation de la requête approximative et rapide <code>knn</code> avec l'option par défaut <code>ElasticsearchConfigurationKnn</code></p></li><li><p>Utilisation de la requête exacte mais plus lente <code>script_score</code> avec l'option <code>ElasticsearchConfigurationScript</code></p></li></ul><p>La prochaine étape consistera à créer une application RAG complète, sur la base de ce que nous avons appris ici.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[IA]]></category>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc873b86c76d1798/6a170293acf088f666be99b3/abd8a4a809064101c037af66b87f28e5ecde03b0-1474x645.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 08 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Tester votre code Java avec des mocks et du vrai Elasticsearch]]></title>
    <description><![CDATA[Apprenez à écrire vos tests automatisés pour Elasticsearch, en utilisant des mocks et des Testcontainers.]]></description>
    <content:encoded><![CDATA[<p>Dans ce billet, nous allons présenter et expliquer deux façons de tester un logiciel en utilisant Elasticsearch comme dépendance d'un système externe. Nous couvrirons les tests utilisant des mocks ainsi que les tests d'intégration, nous montrerons quelques différences pratiques entre eux, et nous donnerons quelques conseils sur la façon de procéder pour chaque style.</p><h2>De bons tests pour la confiance dans le système</h2><p>Un bon test est un test qui augmente la confiance de chaque personne impliquée dans le processus de création et de maintenance d'un système informatique. Les tests ne sont pas censés être cool, rapides ou augmenter artificiellement la couverture du code. Les tests jouent un rôle essentiel à cet égard :</p><ul><li><p>Ce que nous voulons livrer va fonctionner en production.</p></li><li><p>Le système répond aux exigences et aux contrats.</p></li><li><p>Il n'y aura pas de régression à l'avenir.</p></li><li><p>Les développeurs (et les autres membres de l'équipe concernés) sont convaincus que ce qu'ils ont créé fonctionnera.</p></li></ul><p>Bien sûr, cela ne signifie pas que les tests ne peuvent pas être cool, rapides ou augmenter la couverture du code. Plus vite nous pouvons exécuter notre suite de tests, mieux c'est. C'est juste que dans la poursuite de la réduction de la durée globale de la suite de tests, nous ne devrions pas sacrifier la fiabilité, la maintenabilité et la confiance que les tests automatisés nous donnent.</p><p>De bons tests automatisés renforcent la confiance des différents membres de l'équipe :</p><ul><li><p>Les développeurs : ils peuvent confirmer que ce qu'ils font fonctionne (avant même que le code sur lequel ils travaillent ne quitte leur machine).</p></li><li><p>L'équipe d'assurance qualité : elle a moins de tests à effectuer manuellement.</p></li><li><p>Les opérateurs de systèmes et les SRE sont plus détendus, car les systèmes sont plus faciles à déployer et à entretenir.</p></li></ul><p>Dernier point, mais non des moindres : l'architecture d'un système. Nous aimons que les systèmes soient organisés, faciles à entretenir, et que l'architecture soit propre et utile. Cependant, il arrive parfois que l'architecture sacrifie trop à l'excuse connue sous le nom de ": elle est plus testable de cette façon". Il n'y a rien de mal à être très testable, mais lorsque le système est écrit principalement pour être testable au lieu de répondre aux besoins qui justifient son existence, on se retrouve dans une situation où c'est la queue qui l'emporte.</p><h2>Deux types de tests : Mocks &amp; dépendances</h2><p>Les tests peuvent être perçus et donc classés de différentes manières. Dans ce billet, je me concentrerai sur un seul aspect de la division des tests : l'utilisation de mocks (ou stubs, ou fakes, ou ...) par rapport à l'utilisation de vraies dépendances. Dans notre cas, la dépendance est Elasticsearch.</p><p>Les tests utilisant des mocks sont très rapides car ils n'ont pas besoin de démarrer des dépendances externes et tout se passe uniquement en mémoire. Dans le cadre des tests automatisés, on utilise de faux objets au lieu de vrais objets pour tester des parties d'un programme sans utiliser les dépendances réelles. C'est la raison pour laquelle ils sont nécessaires et qu'ils brillent dans tous les tests de réseaux de détection rapide, par exemple. la validation des données. Il n'est pas nécessaire de lancer une base de données et de l'appeler uniquement pour vérifier que les nombres négatifs dans une demande ne sont pas autorisés, par exemple.</p><p>Cependant, l'introduction des mocks a plusieurs implications :</p><ul><li><p>Il n'est pas possible de simuler facilement tout et tout le temps, et les simulations ont donc un impact sur l'architecture du système (ce qui est parfois très bien, parfois moins bien).</p></li><li><p>Les tests fonctionnant sur des mocks peuvent être rapides, mais leur développement peut prendre un certain temps, car les mocks reflétant profondément les systèmes qu'ils imitent ne sont généralement pas fournis gratuitement. Quelqu'un qui sait comment le système fonctionne doit écrire les mocks de la bonne manière, et cette connaissance peut provenir d'une expérience pratique, de l'étude de la documentation, etc.</p></li><li><p>Les objets fictifs doivent être entretenus. Lorsque votre système dépend d'une dépendance externe et que vous devez mettre à jour cette dépendance, quelqu'un doit s'assurer que les mocks imitant la dépendance sont également mis à jour avec tous les changements : cassures, documentés et non documentés (qui peuvent également avoir un impact sur notre système). Cela devient particulièrement pénible lorsque vous voulez mettre à jour une dépendance mais que votre suite de tests (qui n'utilise que des mocks) ne peut pas vous donner l'assurance que tous les cas testés sont garantis de fonctionner.</p></li><li><p>Il faut de la discipline pour s'assurer que les efforts sont consacrés au développement et aux tests du système, et non aux simulacres.</p></li></ul><p>Pour ces raisons, de nombreuses personnes préconisent d'aller exactement dans la direction opposée : ne jamais utiliser de mocks (ou de stubs, etc.), mais s'appuyer uniquement sur les dépendances réelles. Cette approche fonctionne très bien dans les démonstrations ou lorsque le système est minuscule et ne comporte que quelques cas de test générant une couverture importante. Ces tests peuvent être des tests d'intégration (grosso modo : vérification d'une partie d'un système par rapport à certaines dépendances réelles) ou des tests de bout en bout (utilisation simultanée de toutes les dépendances réelles et vérification du comportement du système à toutes les extrémités, tout en jouant les flux de travail de l'utilisateur qui définissent le système comme utilisable et performant). Un avantage évident de cette approche est que nous vérifions également (souvent involontairement) nos hypothèses sur les dépendances et la manière dont nous les intégrons dans le système sur lequel nous travaillons.</p><p>Cependant, lorsque les tests n'utilisent que des dépendances réelles, nous devons prendre en compte les aspects suivants :</p><ul><li><p>Certains scénarios de test n'ont pas besoin de la dépendance réelle (par exemple, pour vérifier les invariants statiques d'une demande).</p></li><li><p>Ces tests ne sont généralement pas exécutés par séries entières sur les machines des développeurs, car l'attente d'un retour d'information prendrait trop de temps.</p></li><li><p>Ils nécessitent plus de ressources sur les machines d'IC, et il faut parfois plus de temps pour régler les choses afin de ne pas perdre de temps sur &amp;.</p></li><li><p>Il n'est pas toujours facile d'initialiser les dépendances avec des données de test.</p></li><li><p>Les tests avec des dépendances réelles sont parfaits pour isoler le code avant une refonte majeure, une migration ou une mise à niveau des dépendances.</p></li><li><p>Il est plus probable qu'il s'agisse de tests opaques, c'est-à-dire qu'ils ne détaillent pas les aspects internes du système testé, mais s'intéressent à leurs résultats.</p></li></ul><h2>Le bon choix : utiliser les deux tests</h2><p>Au lieu de tester votre système avec un seul type de test, vous pouvez vous appuyer sur les deux types de test lorsque c'est utile et essayer d'améliorer votre utilisation des deux.</p><ul><li><p>Exécutez d'abord les tests basés sur des simulacres parce qu'ils sont beaucoup plus rapides, et seulement lorsque tous les tests sont réussis, exécutez les tests de dépendance plus lents seulement après.</p></li><li><p>Choisissez les mocks pour les scénarios où les dépendances externes ne sont pas vraiment nécessaires : lorsque le mocking prendrait trop de temps, le code devrait être massivement modifié juste pour cela ; comptez sur les dépendances externes.</p></li><li><p>Il n'y a rien de mal à tester un morceau de code en utilisant les deux approches, tant que cela a un sens.</p></li></ul><h2>Exemple de SystemUnderTest</h2><p>Pour les sections suivantes, nous allons utiliser un exemple qui se trouve <a href="https://github.com/pioorg/testing-elasticsearch">ici.</a> Il s'agit d'une petite application de démonstration écrite en Java 21, utilisant Maven comme outil de construction, s'appuyant sur le client Elasticsearch et utilisant le dernier ajout d'Elasticsearch, utilisant <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL</a> (le nouveau langage de requête procédural d'Elastic). Si Java n'est pas votre langage de programmation, vous devriez tout de même être en mesure de comprendre les concepts que nous allons aborder ci-dessous et de les transposer dans votre pile. C'est juste que l'utilisation d'un exemple de code réel rend certaines choses plus faciles à expliquer.</p><p>Le site <code>BookSearcher</code> nous aide à gérer la recherche et l'analyse de données, à savoir des livres dans notre cas (comme nous l'avons démontré dans l <a href="https://www.elastic.co/search-labs/blog/esql-queries-to-java-objects">'un des articles précédents)</a>.</p><ul><li><p>Il nécessite Elasticsearch exactement dans la version <code>8.15.x</code> comme seule dépendance (voir <code>isCompatibleWithBackend()</code>), par exemple parce que nous ne sommes pas sûrs que notre code soit compatible avec le futur, et nous sommes sûrs qu'il n'est pas compatible avec le passé. Avant de mettre à niveau Elasticsearch en production vers une version plus récente, nous devons d'abord le tester pour nous assurer que le comportement du système testé reste le même.</p></li><li><p>Nous pouvons l'utiliser pour rechercher le nombre de livres publiés au cours d'une année donnée (voir <code>numberOfBooksPublishedInYear</code>).</p></li><li><p>Nous pouvons également l'utiliser lorsque nous devons analyser notre ensemble de données et trouver les 20 auteurs les plus publiés entre deux années données (voir <code>mostPublishedAuthorsInYears</code>).</p></li></ul>public class BookSearcher {

    private final ElasticsearchClient esClient;

    public BookSearcher(ElasticsearchClient esClient) {
        this.esClient = esClient;
        if (!isCompatibleWithBackend()) {
            throw new UnsupportedOperationException("This is not compatible with backend");
        }
    }

    private boolean isCompatibleWithBackend() {
        try (ResultSet rs = esClient.esql().query(ResultSetEsqlAdapter.INSTANCE, """
            show info
            | keep version
            | dissect version "%{major}.%{minor}.%{patch}"
            | keep major, minor
            | limit 1""")) {
            if (!rs.next()) {
                throw new RuntimeException("No version found");
            }
            return rs.getInt(1) == 8 &amp;&amp; rs.getInt(2) == 15;
        } catch (SQLException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public int numberOfBooksPublishedInYear(int year) {
        try (ResultSet rs = esClient.esql().query(ResultSetEsqlAdapter.INSTANCE, """
            from books
            | where year == ?
            | stats published = count(*) by year
            | limit 1000""", year)) {

            if (rs.next()) {
                return rs.getInt("published");
            }
        } catch (SQLException | IOException e) {
            throw new RuntimeException(e);
        }
        return 0;
    }


    public List&lt;MostPublished&gt; mostPublishedAuthorsInYears(int minYear, int maxYear) {
        assert minYear &lt;= maxYear;
        String query = """
            from books
            | where year &gt;= ? and year &lt;= ?
            | stats first_published = min(year), last_published = max(year), times = count (*) by author
            | eval years_published = last_published - first_published
            | sort years_published desc
            | drop years_published
            | limit 20
            """;

        try {
            Iterable&lt;MostPublished&gt; published = esClient.esql().query(
                ObjectsEsqlAdapter.of(MostPublished.class),
                query,
                minYear,
                maxYear);

            List&lt;MostPublished&gt; mostPublishedAuthors = new ArrayList&lt;&gt;();
            for (MostPublished mostPublished : published) {
                mostPublishedAuthors.add(mostPublished);
            }
            return mostPublishedAuthors;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public record MostPublished(
        String author,
        @JsonProperty("first_published") int firstPublished,
        @JsonProperty("last_published") int lastPublished,
        int times
    ) {
        public MostPublished {
            assert author != null;
            assert firstPublished &lt;= lastPublished;
            assert times &gt; 0;
        }
    }
}
<h2>Tester avec des mocks pour commencer</h2><p>Pour créer les mocks utilisés dans nos tests, nous allons utiliser <a href="https://site.mockito.org/">Mockito</a>, une bibliothèque de mocking très populaire dans l'écosystème Java.</p><p>Nous pourrions commencer par ce qui suit, pour que les simulations soient réinitialisées avant chaque test :</p>public class BookSearcherMockingTest {

    ResultSet mockResultSet;
    ElasticsearchClient esClient;
    ElasticsearchEsqlClient esql;

    @BeforeEach
    void setUpMocks() {
        mockResultSet = mock(ResultSet.class);
        esClient = mock(ElasticsearchClient.class);
        esql = mock(ElasticsearchEsqlClient.class);

    }
}
<p>Comme nous l'avons dit précédemment, tout ne peut pas être facilement testé à l'aide de mocks. Mais il y a des choses que l'on peut (et que l'on doit probablement) faire. Essayons de vérifier que la seule version supportée d'Elasticsearch est <code>8.15.x</code> pour l'instant (à l'avenir, nous pourrions étendre la gamme une fois que nous aurons confirmé que notre système est compatible avec les versions futures) :</p>@Test
void canCreateSearcherWithES_8_15() throws SQLException, IOException{
    // when
    when(esClient.esql()).thenReturn(esql);
    when(esql.query(eq(ResultSetEsqlAdapter.INSTANCE), anyString())).thenReturn(mockResultSet);
    when(mockResultSet.next()).thenReturn(true).thenReturn(false);
    when(mockResultSet.getInt(1)).thenReturn(8);
    when(mockResultSet.getInt(2)).thenReturn(15);

    // then
    Assertions.assertDoesNotThrow(() -&gt; new BookSearcher(esClient));
}
<p>Nous pouvons vérifier de la même manière (simplement en renvoyant une version mineure différente) que notre site <code>BookSearcher</code> ne fonctionnera pas encore avec <code>8.16.x</code>, car nous ne sommes pas sûrs qu'il sera compatible avec lui :</p>@Test
void cannotCreateSearcherWithoutES_8_15() throws SQLException, IOException {
    // when
    when(esClient.esql()).thenReturn(esql);
    when(esql.query(eq(ResultSetEsqlAdapter.INSTANCE), anyString())).thenReturn(mockResultSet);
    when(mockResultSet.next()).thenReturn(true).thenReturn(false);
    when(mockResultSet.getInt(1)).thenReturn(8);
    when(mockResultSet.getInt(2)).thenReturn(16);

    // then
    Assertions.assertThrows(UnsupportedOperationException.class, () -&gt; new BookSearcher(esClient));
}
<p>Voyons maintenant comment nous pouvons obtenir quelque chose de similaire en testant contre un vrai Elasticsearch. Pour cela, nous allons utiliser le <a href="https://java.testcontainers.org/modules/elasticsearch/">module Elasticsearch de Testcontainers</a>, qui n'a qu'une seule exigence : il a besoin d'un accès à Docker, car il exécute des conteneurs Docker pour vous. D'un certain point de vue, Testcontainers est simplement un moyen d'exploiter les conteneurs Docker, mais au lieu de le faire dans votre Docker Desktop (ou similaire), dans votre CLI, ou dans des scripts, vous pouvez exprimer vos besoins dans le langage de programmation que vous connaissez. Cela permet de récupérer des images, de démarrer des conteneurs, de les ramasser après les tests, de copier des fichiers dans les deux sens, d'exécuter des commandes, d'examiner les journaux, etc. directement à partir de votre code de test.</p><p>Le talon pourrait ressembler à ceci :</p>@Testcontainers
public class BookSearcherIntTest {

    static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.15.0";
    static final JacksonJsonpMapper JSONP_MAPPER = new JacksonJsonpMapper();

    RestClientTransport transport;
    ElasticsearchClient client;

    @Container
    ElasticsearchContainer elasticsearch = new ElasticsearchContainer(ELASTICSEARCH_IMAGE);

    @BeforeEach
    void setupClient() {
        transport = // setup transport here
        client = new ElasticsearchClient(transport);
    }

    @AfterEach
    void closeClient() throws IOException {
        if (transport != null) {
            transport.close();
        }
    }

}
<p>Dans cet exemple, nous nous appuyons sur l'<a href="https://java.testcontainers.org/test_framework_integration/junit_5/">intégration JUnit de Testcontainers</a> avec <code>@Testcontainers</code> et <code>@Container</code>, ce qui signifie que nous n'avons pas à nous soucier de démarrer Elasticsearch avant nos tests et de l'arrêter après. La seule chose à faire est de créer le client avant chaque test et de le fermer après chaque test (pour éviter les fuites de ressources, qui pourraient avoir un impact sur des suites de tests plus importantes).</p><p>Annoter un champ non statique avec <code>@Container</code> signifie qu'un nouveau conteneur sera démarré pour chaque test, nous n'avons donc pas à nous soucier des données périmées ou de la réinitialisation de l'état du conteneur. Cependant, avec de nombreux tests, cette approche pourrait ne pas donner de bons résultats, c'est pourquoi nous allons la comparer à d'autres solutions dans l'un des prochains articles.</p><p><strong>Remarque :</strong></p>En vous appuyant sur <code>docker.elastic.co</code> (le dépôt d'images Docker officiel d'Elastic), vous évitez d'épuiser vos limites sur le hub Docker.

Il est également recommandé d'utiliser la même version de votre dépendance dans votre environnement de test et de production, afin de garantir une compatibilité maximale. Nous recommandons également d'être précis dans la sélection de la version, pour cette raison, il n'y a pas de balise <code>latest</code> pour les images Elasticsearch.<h2>Connexion à Elasticsearch dans les tests</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html">Le client Java Elasticsearch</a> est capable de se connecter à Elasticsearch fonctionnant dans un conteneur de test même si la sécurité et SSL/TLS sont activés (ce qui est le cas par défaut pour les versions 8.x, c'est pourquoi nous n'avons pas eu à spécifier quoi que ce soit relatif à la sécurité dans la déclaration du conteneur). En supposant que l'Elasticsearch que vous utilisez en production dispose également de TLS et d'une certaine sécurité, il est recommandé d'opter pour une configuration de test d'intégration aussi proche que possible du scénario de production, et donc de ne pas les désactiver dans les tests.</p><p>Comment obtenir les données nécessaires à la connexion, en supposant que le conteneur soit affecté au champ ou à la variable <code>elasticsearch</code>:</p><ul><li><p><code>elasticsearch.getHost()</code> vous donnera l'hôte sur lequel le conteneur fonctionne (qui la plupart du temps sera probablement <code>"localhost"</code>, mais ne le codifiez pas en dur car parfois, en fonction de votre configuration, il peut s'agir d'un autre nom, donc l'hôte doit toujours être obtenu dynamiquement).</p></li><li><p><code>elasticsearch.getMappedPort(9200)</code> donnera le port hôte que vous devez utiliser pour vous connecter à Elasticsearch à l'intérieur du conteneur (parce qu'à chaque fois que vous démarrez le conteneur, le port extérieur est différent, donc cela doit être un appel dynamique également).</p></li><li><p>Sauf s'ils ont été écrasés, le nom d'utilisateur et le mot de passe par défaut sont respectivement <code>"elastic"</code> et <code>"changeme"</code>.</p></li><li><p>Si aucun certificat SSL/TLS n'a été spécifié lors de la configuration du conteneur et que la connectivité sécurisée n'est pas désactivée (ce qui est le comportement par défaut à partir des versions 8.x), un certificat auto-signé est généré. Pour lui faire confiance (par exemple comme le <a href="https://curl.se/docs/manpage.html#--cacert">fait cURL</a>), le certificat peut être obtenu en utilisant <code>elasticsearch.caCertAsBytes()</code> (qui renvoie <code>Optional&lt;byte[]&gt;</code>), ou une autre méthode pratique consiste à obtenir <code>SSLContext</code> en utilisant <code>createSslContextFromCa()</code>.</p></li></ul><p>Le résultat global pourrait ressembler à ceci :</p>BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level rest client
RestClient restClient = RestClient.builder(new HttpHost(elasticsearch.getHost(), elasticsearch.getMappedPort(9200), "https"))
    .setHttpClientConfigCallback(httpClientBuilder -&gt;
        httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
            .setSSLContext(elasticsearch.createSslContextFromCa())
    )
    .build();

// The RestClientTransport is mainly for serialization/deserialization
RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());

// The official Java API Client for Elasticsearch
ElasticsearchClient client = new ElasticsearchClient(transport);
<p>Un autre exemple de création d'une instance de <code>ElasticsearchClient</code> se trouve dans le <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherIntTest.java#L61">projet de démonstration</a>.</p><p><strong>Remarque</strong>:</p>Pour la création de clients dans des environnements de production, veuillez vous référer à la <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/connecting.html#_verifying_https_with_a_certificate_fingerprint">documentation.</a><h2>Premier test d'intégration</h2><p>Notre tout premier test, qui consiste à vérifier que nous pouvons créer <code>BookSearcher</code> à l'aide de la version 8.15.x d'Elasticsearch, pourrait ressembler à ceci :</p>@Test
void canCreateClientWithContainerRunning_8_15() {
    Assertions.assertDoesNotThrow(() -&gt; new BookSearcher(client));
}
<p>Comme vous pouvez le constater, nous n'avons besoin de rien d'autre. Nous n'avons pas besoin de simuler la version renvoyée par Elasticsearch, la seule chose que nous devons faire est de fournir à <code>BookSearcher</code> un client connecté à une instance réelle d'Elasticsearch, qui a été démarrée pour nous par Testcontainers.</p><h2>Les tests d'intégration se soucient moins des aspects internes</h2><p>Faisons une petite expérience : supposons que nous devions cesser d'extraire des données de l'ensemble de résultats à l'aide d'indices de colonnes, mais que nous devions nous appuyer sur les noms de colonnes. Ainsi, dans la méthode <code>isCompatibleWithBackend</code>, au lieu de</p>return rs.getInt(1) == 8 &amp;&amp; rs.getInt(2) == 15;
<p>que nous allons avoir :</p>return rs.getInt("major") == 8 &amp;&amp; rs.getInt("minor") == 15;
<p>Lorsque nous réexécutons les deux tests, nous remarquons que le test d'intégration avec le vrai Elasticsearch passe toujours sans problème. Cependant, les tests utilisant des simulacres ont cessé de fonctionner, parce que nous avons simulé des appels tels que <code>rs.getInt(int)</code>, et non <code>rs.getInt(String)</code>. Pour les faire passer, nous devons maintenant soit les simuler à la place, soit les simuler tous les deux, en fonction des autres cas d'utilisation que nous avons dans notre suite de tests.</p><h2>Les tests d'intégration peuvent être un canon pour tuer une mouche</h2><p>Les tests d'intégration permettent de vérifier le comportement du système, même si des dépendances externes ne sont pas nécessaires. Cependant, cette utilisation est généralement une perte de temps et de ressources d'exécution. Examinons la méthode <code>mostPublishedAuthorsInYears(int minYear, int maxYear)</code>. Les deux premières lignes sont les suivantes :</p>assert minYear &lt;= maxYear;
String query = // here goes the query
<p>La première instruction vérifie une condition qui ne dépend pas d'Elasticsearch (ou de toute autre dépendance externe) de quelque manière que ce soit. Par conséquent, il n'est pas nécessaire de lancer des conteneurs pour vérifier simplement que si <code>minYear</code> est supérieur à <code>maxYear</code>, une exception est levée.</p><p>Un simple test de simulation, qui est également rapide et peu gourmand en ressources, est plus que suffisant pour s'en assurer. Après avoir mis en place les simulacres, nous pouvons simplement opter pour :</p>BookSearcher systemUnderTest = new BookSearcher(esClient);

Assertions.assertThrows(
    AssertionError.class,
    () -&gt; systemUnderTest.mostPublishedAuthorsInYears(2012, 2000)
);
<p>Lancer une dépendance, au lieu de faire du mocking, serait un gaspillage dans <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherMockingTest.java#L89">ce cas de test</a> car il n'y a aucune chance de faire un appel significatif pour cette dépendance.</p><p>Cependant, pour vérifier le comportement à partir de <code>String query = ...</code>, que la requête est écrite correctement, les résultats sont conformes aux attentes : la bibliothèque du client est capable d'envoyer des requêtes et des réponses correctes, il n'y a pas de changement de syntaxe et il est donc beaucoup plus facile d'utiliser un test d'intégration, par exemple :</p>@BeforeEach
void setupDataInContainer() {
    // here we initialise data in the Elasticsearch running in a container
}

@Test
void shouldGiveMostPublishedAuthorsInGivenYears() {
    var systemUnderTest = new BookSearcher(client);
    var list = systemUnderTest.mostPublishedAuthorsInYears(1800, 2010);
    Assertions.assertEquals("Beatrix Potter", list.get(12).author(), "Beatrix Potter was 13th most published author between 1800 and 2010");
}
<p>De cette façon, nous pouvons être assurés que lorsque nous envoyons nos données à Elasticsearch (dans cette version ou dans toute version future vers laquelle nous choisirons de migrer), notre requête nous donnera exactement ce que nous attendions : le format des données n'a pas changé, la requête est toujours valide, et tous les middleware (clients, pilotes, sécurité, etc.) continueront à fonctionner. Nous n'avons pas à nous préoccuper de maintenir les mocks à jour, le seul changement nécessaire pour assurer la compatibilité avec, par exemple, le système de gestion de l'information de l'entreprise. <code>8.15</code> changerait cela :</p>static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.15.0";
<p>Il en va de même si vous décidez par exemple de utiliser le bon vieux QueryDSL au lieu de ES|QL : les résultats que vous recevez de la requête (quelle que soit la langue) devraient toujours être les mêmes.</p><h2>Utiliser les deux approches si nécessaire</h2><p>Le cas de la méthode <code>mostPublishedAuthorsInYears</code> illustre le fait qu'une seule méthode peut être testée à l'aide des deux méthodes. Et peut-être même qu'il devrait l'être.</p><ul><li><p>Le fait de n'utiliser que des maquettes signifie que nous devons maintenir la maquette et que nous n'avons aucune confiance dans la mise à jour de notre système.</p></li><li><p>Le fait de n'utiliser que des tests d'intégration signifierait que nous gaspillons beaucoup de ressources sans en avoir besoin.</p></li></ul><h2>Récapitulons</h2><ul><li><p>Il est possible d'utiliser des tests d'intégration et de simulation avec Elasticsearch.</p></li><li><p>Utiliser des tests de simulation comme filet de détection rapide et seulement s'ils passent avec succès, lancer des tests avec des dépendances (par exemple en utilisant <code>./mvnw test '-Dtest=!TestInt*' &amp;&amp; ./mvnw test '-Dtest=TestInt*'</code> ou les plugins <a href="https://maven.apache.org/surefire/maven-failsafe-plugin/">Failsafe</a> et <a href="https://maven.apache.org/surefire/maven-surefire-plugin/">Surefire</a> ).</p></li><li><p>Utilisez des mocks pour tester le comportement de votre système ("lignes de code") lorsque l'intégration avec des dépendances externes n'a pas vraiment d'importance (ou pourrait même être ignorée).</p></li><li><p>Utilisez des tests d'intégration pour vérifier vos hypothèses sur les systèmes externes et leur intégration.</p></li><li><p>N'hésitez pas à tester les deux approches - si cela se justifie - en fonction des points ci-dessus.</p></li></ul><p>On pourrait faire remarquer que le fait d'être aussi strict en ce qui concerne la version (dans notre cas <code>8.15.x</code>) est excessif. L'utilisation de la seule étiquette de la version pourrait l'être, mais sachez que dans ce billet, elle sert de représentation de toutes les autres caractéristiques susceptibles de changer entre les versions.</p><p>Dans le <a href="https://www.elastic.co/search-labs/blog/automated-integration-tests-faster-elasticsearch">prochain article de la série</a>, nous verrons comment initialiser Elasticsearch dans un conteneur de test, avec des ensembles de données de test. Faites-nous savoir si vous avez construit quelque chose à partir de ce blog ou si vous avez des questions sur nos <a href="https://discuss.elastic.co/">forums de discussion</a> et le <a href="https://communityinviter.com/apps/elasticstack/elastic-community">canal Slack de la communauté.</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/tests-with-mocks-and-real-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/tests-with-mocks-and-real-elasticsearch</guid>
    <category><![CDATA[Java]]></category>
    <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e4d003f09dfbe50/6a1709aba929cf810aae0957/b6bb727815ebdb844aeb36d5c44cdf3657f0e4bc-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 03 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Présentation de LangChain4j pour simplifier l'intégration de LLM dans les applications Java]]></title>
    <description><![CDATA[LangChain4j (LangChain pour Java) est un ensemble d'outils puissants pour construire votre application RAG en Java simple.]]></description>
    <content:encoded><![CDATA[<p>Le <a href="https://docs.langchain4j.dev/">cadre LangChain4j</a> a été créé en 2023 avec <a href="https://github.com/langchain4j/langchain4j/blob/main/README.md#introduction">cet objectif</a>:</p>L'objectif de LangChain4j est de simplifier l'intégration des LLM dans les applications Java.<p>LangChain4j fournit une méthode standard pour :</p><ul><li><p>créer des embeddings (vecteurs) à partir d'un contenu donné, par exemple un texte</p></li><li><p>stocker les embeddings dans un magasin d'embeddings</p></li><li><p>recherche de vecteurs similaires dans le magasin d'intégration</p></li><li><p>discuter avec les LLM</p></li><li><p>utiliser une mémoire de chat pour se souvenir du contexte d'une discussion avec un LLM</p></li></ul><p>Cette liste n'est pas exhaustive et la communauté LangChain4j est toujours en train d'implémenter de nouvelles fonctionnalités.</p><p>Ce billet couvrira les premières parties principales du cadre.</p><h2>Ajouter LangChain4j OpenAI à notre projet</h2><p>Comme dans tous les projets Java, c'est juste une question de dépendances. Nous utiliserons ici Maven, mais la même chose pourrait être réalisée avec n'importe quel autre gestionnaire de dépendances.</p><p>Comme première étape du projet que nous voulons construire ici, nous utiliserons OpenAI, il nous suffit donc d'ajouter l'artefact <code>langchain4j-open-ai</code>:</p>&lt;properties&gt;
  &lt;langchain4j.version&gt;0.34.0&lt;/langchain4j.version&gt;
&lt;/properties&gt;

&lt;dependencies&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;langchain4j-open-ai&lt;/artifactId&gt;
    &lt;version&gt;${langchain4j.version}&lt;/version&gt;
  &lt;/dependency&gt;
&lt;/dependencies&gt;
<p>Pour le reste du code, nous utiliserons soit notre propre clé API, que vous pouvez obtenir en créant un compte auprès d'<a href="https://platform.openai.com/signup/">OpenAI</a>, soit celle fournie par le projet LangChain4j à des fins de démonstration uniquement :</p>static String getOpenAiApiKey() {
  String apiKey = System.getenv(API_KEY_ENV_NAME);
  if (apiKey == null || apiKey.isEmpty()) {
    Logger.warn("Please provide your own key instead using [{}] env variable", API_KEY_ENV_NAME);
    return "demo";
  }
  return apiKey;
}
<p>Nous pouvons maintenant créer une instance de notre ChatLanguageModel :</p>ChatLanguageModel model = OpenAiChatModel.withApiKey(getOpenAiApiKey());
<p>Enfin, nous pouvons poser une question simple et obtenir une réponse :</p>String answer = model.generate("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);
<p>La réponse donnée pourrait être quelque chose comme :</p>Thomas Pesquet is a French aerospace engineer, pilot, and European Space Agency astronaut.
He was selected as a member of the European Astronaut Corps in 2009 and has since completed 
two space missions to the International Space Station, including serving as a flight engineer 
for Expedition 50/51 in 2016-2017. Pesquet is known for his contributions to scientific 
research and outreach activities during his time in space.
<p>Si vous souhaitez exécuter ce code, consultez la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step1AiChatTest.java">Step1AiChatTest.java</a>.</p><h2>Fournir plus de contexte avec langchain4j</h2><p>Ajoutons l'artefact <code>langchain4j</code>:</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;
<p>Celui-ci fournit un ensemble d'outils qui peuvent nous aider à construire une intégration LLM plus avancée pour construire notre assistant. Ici, nous allons simplement créer une interface <code>Assistant</code> qui fournit la méthode <code>chat</code> qui appellera automatiquement la méthode <code>ChatLanguageModel</code> que nous avons définie plus tôt :</p>interface Assistant {
  String chat(String userMessage);
}
<p>Il suffit de demander à la classe LangChain4j <code>AiServices</code> de construire une instance pour nous :</p>Assistant assistant = AiServices.create(Assistant.class, model);
<p>Puis appeler la méthode <code>chat(String)</code>:</p>String answer = assistant.chat("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);
<p>Le comportement est le même qu'auparavant. Pourquoi avons-nous donc modifié le code ? Tout d'abord, c'est plus élégant, mais en plus, vous pouvez maintenant donner des instructions au LLM à l'aide de simples annotations :</p>interface Assistant {
  @SystemMessage("Please answer in a funny way.")
  String chat(String userMessage);
}
<p>C'est maintenant chose faite :</p>Ah, Thomas Pesquet is actually a super secret spy disguised as an astronaut! 
He's out there in space fighting aliens and saving the world one spacewalk at a time. 
Or maybe he's just a really cool French astronaut who has been to the International 
Space Station. But my spy theory is much more exciting, don't you think?
<p>Si vous souhaitez exécuter ce code, consultez la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step2AssistantTest.java">Step2AssistantTest.java</a>.</p><h2>Passer à un autre LLM : langchain4j-ollama</h2><p>Nous pouvons utiliser le grand <a href="https://ollama.com/">projet Ollama</a>. Il est utile d'exécuter un LLM localement sur votre machine.</p><p>Ajoutons l'artefact <code>langchain4j-ollama</code>:</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-ollama&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;
<p>Comme nous exécutons l'exemple de code à l'aide de tests, ajoutons des <a href="https://java.testcontainers.org/">conteneurs de test</a> à notre projet :</p>&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;ollama&lt;/artifactId&gt;
  &lt;version&gt;1.20.1&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
<p>Nous pouvons maintenant démarrer/arrêter les conteneurs Docker :</p>static String MODEL_NAME = "mistral";
static String DOCKER_IMAGE_NAME = "langchain4j/ollama-" + MODEL_NAME + ":latest";

static OllamaContainer ollama = new OllamaContainer(
  DockerImageName.parse(DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("ollama/ollama"));

@BeforeAll
public static void setup() {
  ollama.start();
}

@AfterAll
public static void teardown() {
  ollama.stop();
}
<p>Nous "devons simplement" modifier l'objet <code>model</code> pour qu'il devienne un <code>OllamaChatModel</code> au lieu du <code>OpenAiChatModel</code> que nous utilisions précédemment :</p>OllamaChatModel model = OllamaChatModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();
<p>Notez que l'extraction de l'image avec son modèle peut prendre un certain temps, mais au bout d'un moment, vous pouvez obtenir la réponse :</p>Oh, Thomas Pesquet, the man who single-handedly keeps the French space program running 
while sipping on his crisp rosé and munching on a baguette! He's our beloved astronaut 
with an irresistible accent that makes us all want to learn French just so we can 
understand him better. When he's not floating in space, he's probably practicing his 
best "je ne sais quoi" face for the next family photo. Vive le Thomas Pesquet! 
🚀🌍🇫🇷 #FrenchSpaceHero
<h2>Meilleure mémoire</h2><p>Si nous posons plusieurs questions, par défaut le système ne se souviendra pas des questions et réponses précédentes. Ainsi, si nous posons la question suivante : "Quand est-il né ?", notre application répondra :</p>Oh, you're asking about this legendary figure from history, huh? Well, let me tell 
you a hilarious tale! He was actually born on Leap Year's Day, but only every 400 
years! So, do the math... if we count backwards from 2020 (which is also a leap year), 
then he was born in... *drumroll please* ...1600! Isn't that a hoot? But remember 
folks, this is just a joke, and historical records may vary.
<p>Ce qui est absurde. Nous devrions plutôt utiliser la <a href="https://docs.langchain4j.dev/tutorials/chat-memory">mémoire de chat :</a></p>ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
Assistant assistant = AiServices.builder(Assistant.class)
  .chatLanguageModel(model)
  .chatMemory(chatMemory)
  .build();
<p>En posant les mêmes questions maintenant, on obtient une réponse significative :</p>Oh, Thomas Pesquet, the man who was probably born before sliced bread but after dinosaurs! 
You know, around the time when people started putting wheels on suitcases and calling it 
a revolution. So, roughly speaking, he came into this world somewhere in the late 70s or 
early 80s, give or take a year or two - just enough time for him to grow up, become an 
astronaut, and make us all laugh with his space-aged antics! Isn't that a hoot? 
*laughs maniacally*
<h2>Conclusion</h2><p>Dans <a href="https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store">le prochain article</a>, nous découvrirons comment poser des questions à notre ensemble de données privé en utilisant Elasticsearch comme magasin d'intégration. Cela nous permettra de passer à la vitesse supérieure en matière de recherche de candidatures.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[IA]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0435ed6d14579089/6a17e79ae8fbce7e433a192f/cf129b8b25fbe7204e2adca8fca5fec04207f096-720x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 23 Sep 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>