<?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/de/search-labs/blog/category/java-programming</link>
    </image>
    <link>https://www.elastic.co/de/search-labs/blog/category/java-programming</link>
    <atom:link href="https://www.elastic.co/de/search-labs/rss/category/java-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[de]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 20:51:29 GMT</lastBuildDate>
  <item>
    <title><![CDATA[LangChain4j mit Elasticsearch als Einbettungsspeicher]]></title>
    <description><![CDATA[LangChain4j (LangChain für Java) verwendet Elasticsearch als eingebetteten Speicher. Erfahren Sie, wie Sie damit Ihre RAG-Anwendung in reinem Java erstellen können.]]></description>
    <content:encoded><![CDATA[<p>
Im <a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">vorherigen Beitrag</a> haben wir herausgefunden, was LangChain4j ist und wie es funktioniert:</p><ul><li><p>Führen Sie eine Diskussion mit LLMs, indem Sie ein <code>ChatLanguageModel</code> und ein <code>ChatMemory</code></p></li><li><p>Die Chat-Historie wird im Speicher gehalten, um den Kontext einer früheren Diskussion mit einem LLM wieder abrufen zu können.</p></li></ul><p>Dieser Blogbeitrag erklärt, wie man:</p><ul><li><p>Vektor-Einbettungen aus Textbeispielen erstellen</p></li><li><p>Vektoreinbettungen im Elasticsearch-Einbettungsspeicher speichern </p></li><li><p>Suche nach ähnlichen Vektoren</p></li></ul><h2>Einbettungen erstellen</h2><p>Um Einbettungen zu erstellen, müssen wir ein <code>EmbeddingModel</code> definieren, das verwendet werden soll. Wir können beispielsweise dasselbe Mistral-Modell verwenden, das wir im <a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">vorherigen Beitrag</a> verwendet haben. Es lief mit ollama:</p>EmbeddingModel model = OllamaEmbeddingModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();<p>Ein Modell ist in der Lage, aus Text Vektoren zu generieren. Hier können wir die Anzahl der vom Modell generierten Dimensionen überprüfen:</p>Logger.info("Embedding model has {} dimensions.", model.dimension());
// This gives: Embedding model has 4096 dimensions.<p>Um Vektoren aus einem Text zu generieren, können wir Folgendes verwenden:</p>Response&lt;Embedding&gt; response = model.embed("A text here");<p>Oder wenn wir auch Metadaten bereitstellen möchten, um nach Dingen wie Text, Preis, Veröffentlichungsdatum oder Ähnlichem filtern zu können, können wir <code>Metadata.from()</code> verwenden. Zum Beispiel fügen wir hier den Spielnamen als Metadatenfeld hinzu:</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>Wenn Sie diesen Code ausführen möchten, sehen Sie sich bitte die Klasse <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step5EmbedddingsTest.java">Step5EmbedddingsTest.java</a> an.</p><h2>Elasticsearch hinzufügen, um unsere Vektoren zu speichern</h2><p>LangChain4j bietet einen In-Memory-Embedding-Speicher. Dies ist nützlich, um einfache Tests durchzuführen:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore = new InMemoryEmbeddingStore&lt;&gt;();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Bei deutlich größeren Datensätzen funktioniert das natürlich nicht, da dieser Datenspeicher alles im Arbeitsspeicher ablegt und wir auf unseren Servern nicht über unbegrenzten Speicherplatz verfügen. Wir könnten unsere Einbettungen also stattdessen in Elasticsearch speichern, das per Definition "elastisch" ist und mit Ihren Daten skalieren kann. Dazu fügen wir Elasticsearch zu unserem Projekt hinzu:</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>Wie Sie bemerkt haben, haben wir dem Projekt auch das Elasticsearch TestContainers-Modul hinzugefügt, sodass wir eine Elasticsearch-Instanz aus unseren Tests heraus starten können:</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>Um Elasticsearch als Einbettungsspeicher zu verwenden, müssen Sie „nur“ vom LangChain4j-In-Memory-Datenspeicher zum Elasticsearch-Datenspeicher wechseln:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Dadurch werden Ihre Vektoren in Elasticsearch in einem <code>default</code> -Index gespeichert. Sie können den Indexnamen auch in einen aussagekräftigeren Namen ändern:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .indexName("games")
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Wenn Sie diesen Code ausführen möchten, sehen Sie sich bitte die Klasse <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step6ElasticsearchEmbedddingsTest.java">Step6ElasticsearchEmbedddingsTest.java</a> an.</p><h2>Suche nach ähnlichen Vektoren</h2><p>Um nach ähnlichen Vektoren zu suchen, müssen wir zunächst unsere Frage mithilfe des gleichen Modells, das wir zuvor verwendet haben, in eine Vektordarstellung umwandeln. Das haben wir schon gemacht, also ist es nicht schwer, das noch einmal zu tun. Beachten Sie, dass wir in diesem Fall die Metadaten nicht benötigen:</p>String question = "I want to pilot a car";
Embedding questionAsVector = model.embed(question).content();<p>Wir können mit dieser Repräsentation unserer Frage eine Suchanfrage erstellen und den Embedding-Speicher bitten, die ersten Top-Vektoren zu finden:</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Wir können die Ergebnisse nun durchlaufen und einige Informationen ausgeben, wie zum Beispiel den Spielnamen, der aus den Metadaten stammt, und die Punktzahl:</p>result.matches().forEach(m -&gt; Logger.info("{} - score [{}]",
  m.embedded().metadata().getString("gameName"), m.score()));<p>Wie zu erwarten, ist „Out Run“ der erste Treffer:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ca0dcfdb1a9c94f/6a170291cf4f256938b2d017/140b6a962e5edbb4870419250e30bfb815b0d73e-640x480.gif" alt="Out Run" />Out Run - score [0.86672974]
The Secret of Monkey Island - score [0.85569763]<p>Wenn Sie diesen Code ausführen möchten, sehen Sie sich bitte die Klasse <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L110-L129">Step7SearchForVectorsTest.java</a> an. </p><h2>Hinter den Kulissen</h2><p>Die Standardkonfiguration für den Elasticsearch Embedding Store verwendet im Hintergrund die <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-knn-query.html">approximative kNN-Abfrage</a> .</p>POST games/_search
{
  "query" : {
    "knn": {
      "field": "vector",
      "query_vector": [-0.019137882, /* ... */, -0.0148779955]
    }
  }
}<p>Dies könnte jedoch geändert werden, indem dem Embedding-Speicher eine andere Konfiguration (<code>ElasticsearchConfigurationScript</code>) als die Standardkonfiguration (<code>ElasticsearchConfigurationKnn</code>) bereitgestellt wird:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .configuration(ElasticsearchConfigurationScript.builder().build())
    .indexName("games")
    .restClient(client)
    .build();<p>Die <code>ElasticsearchConfigurationScript</code> -Implementierung führt im Hintergrund eine <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"><code>script_score</code></a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html">-Abfrage</a> mit einer <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> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine">-Funktion</a> aus.</p><p>Grundsätzlich gilt beim Aufruf:</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Dies nennt man nun:</p>POST games/_search
{
  "query": {
    "script_score": {
      "script": {
        "source": "(cosineSimilarity(params.query_vector, 'vector') + 1.0) / 2",
        "params": {
          "queryVector": [-0.019137882, /* ... */, -0.0148779955]
        }
      }
    }
  }
}<p>In diesem Fall ändert sich das Ergebnis hinsichtlich der "Ordnung" nicht, sondern nur die Punktzahl wird angepasst, da der <code>cosineSimilarity</code> -Aufruf keine Näherung verwendet, sondern den Kosinus für jeden der übereinstimmenden Vektoren berechnet:</p>Out Run - score [0.871952]
The Secret of Monkey Island - score [0.86380446]<p>Wenn Sie diesen Code ausführen möchten, sehen Sie sich bitte die Klasse <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L132-L155">Step7SearchForVectorsTest.java</a> an.</p><h2>Fazit</h2><p>Wir haben behandelt, wie einfach Sie Einbettungen aus Ihrem Text generieren können und wie Sie die nächsten Nachbarn in Elasticsearch speichern und suchen können, und zwar mit zwei verschiedenen Ansätzen:</p><ul><li><p>Verwendung der approximativen und schnellen <code>knn</code> -Abfrage mit der Standardoption <code>ElasticsearchConfigurationKnn</code></p></li><li><p>Verwendung der exakten, aber langsameren <code>script_score</code> -Abfrage mit der <code>ElasticsearchConfigurationScript</code> -Option</p></li></ul><p>Im nächsten Schritt geht es darum, eine vollständige RAG-Anwendung zu entwickeln, basierend auf dem, was wir hier gelernt haben.</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[KI]]></category>
    <category><![CDATA[Vektordatenbank]]></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[Testen Ihres Java-Codes mit Mocks und echtem Elasticsearch]]></title>
    <description><![CDATA[Lernen Sie, wie Sie Ihre automatisierten Tests für Elasticsearch mithilfe von Mocks und Testcontainers schreiben.]]></description>
    <content:encoded><![CDATA[<p>In diesem Beitrag stellen wir zwei Methoden zum Testen von Software vor, bei denen Elasticsearch als externe Systemabhängigkeit verwendet wird, und erläutern diese. Wir werden sowohl Tests mit Mock-Objekten als auch Integrationstests behandeln, einige praktische Unterschiede zwischen ihnen aufzeigen und Hinweise geben, wo man sich für den jeweiligen Teststil entscheiden kann.</p><h2>Gute Tests für das Systemvertrauen</h2><p>Ein guter Test ist ein Test, der das Vertrauen aller am Prozess der Erstellung und Wartung eines IT-Systems Beteiligten stärkt. Tests sollen nicht cool oder schnell sein oder die Codeabdeckung künstlich erhöhen. Tests spielen eine entscheidende Rolle, um Folgendes sicherzustellen:</p><ul><li><p>Unser Ziel ist es, ein Produkt zu liefern, das in der Produktion funktioniert.</p></li><li><p>Das System erfüllt die Anforderungen und die Verträge.</p></li><li><p>Es wird in Zukunft keine Rückschritte mehr geben.</p></li><li><p>Die Entwickler (und andere beteiligte Teammitglieder) sind zuversichtlich, dass das, was sie geschaffen haben, funktionieren wird.</p></li></ul><p>Das heißt natürlich nicht, dass Tests nicht cool, schnell oder leistungsfähiger sein oder die Codeabdeckung erhöhen können. Je schneller wir unsere Testsuite ausführen können, desto besser. Es geht einfach darum, dass wir bei dem Bestreben, die Gesamtdauer der Testsuite zu verkürzen, nicht die Zuverlässigkeit, Wartbarkeit und das Vertrauen in die automatisierten Tests beeinträchtigen sollten.</p><p>Gute automatisierte Tests stärken das Selbstvertrauen der verschiedenen Teammitglieder:</p><ul><li><p>Entwickler: Sie können sich vergewissern, dass das, was sie tun, auch funktioniert (noch bevor der Code, an dem sie arbeiten, ihren Rechner verlässt).</p></li><li><p>Qualitätssicherungsteam: Sie haben weniger manuelle Tests durchzuführen.</p></li><li><p>Systembetreiber und SREs: sind entspannter, da die Systeme einfacher zu implementieren und zu warten sind.</p></li></ul><p>Zu guter Letzt: die Architektur eines Systems. Wir lieben es, wenn Systeme gut organisiert und wartungsfreundlich sind und eine klare Architektur aufweisen, die ihren Zweck erfüllt. Manchmal sieht man jedoch eine Architektur, die zu viel opfert für die Ausrede, dass es so besser testbar sei. Es ist nichts Schlechtes daran, sehr gut testbar zu sein – nur wenn das System in erster Linie so geschrieben wird, dass es testbar ist, anstatt den Bedürfnissen zu dienen, die seine Existenz rechtfertigen, sehen wir eine Situation, in der der Schwanz mit dem Hund wedelt.</p><h2>Zwei Arten von Tests: Mocks und Abhängigkeiten</h2><p>Es gibt viele Möglichkeiten, die Tests zu betrachten und somit zu klassifizieren. In diesem Beitrag werde ich mich nur auf einen Aspekt der Aufteilung der Tests konzentrieren: die Verwendung von Mocks (oder Stubs, oder Fakes, oder ...) im Vergleich zur Verwendung echter Abhängigkeiten. In unserem Fall ist die Abhängigkeit Elasticsearch.</p><p>Tests mit Mocks sind sehr schnell, da keine externen Abhängigkeiten gestartet werden müssen und alles ausschließlich im Speicher stattfindet. Beim Mocking in automatisierten Tests werden gefälschte Objekte anstelle von realen Objekten verwendet, um Teile eines Programms zu testen, ohne die tatsächlichen Abhängigkeiten zu nutzen. Aus diesem Grund werden sie benötigt und deshalb glänzen sie in Schnellerkennungsnetztests, z. B. Validierung der Eingabe. Es ist beispielsweise nicht nötig, eine Datenbank zu starten und sie aufzurufen, nur um zu überprüfen, ob negative Zahlen in einer Anfrage nicht zulässig sind.</p><p>Die Einführung von Mock-Objekten hat jedoch mehrere Konsequenzen:</p><ul><li><p>Nicht alles und nicht alles lässt sich jederzeit einfach simulieren, daher haben Simulationen Auswirkungen auf die Architektur des Systems (was manchmal gut, manchmal weniger gut ist).</p></li><li><p>Tests, die auf simulierten Systemen laufen, sind zwar schnell, aber die Entwicklung solcher Tests kann recht lange dauern, da die simulierten Systeme, die die simulierten Systeme genau widerspiegeln, in der Regel nicht kostenlos zur Verfügung gestellt werden. Wer weiß, wie das System funktioniert, muss die Mocks auf die richtige Weise schreiben, und dieses Wissen kann aus praktischer Erfahrung, dem Studium der Dokumentation usw. stammen.</p></li><li><p>Mockups müssen gepflegt werden. Wenn Ihr System von einer externen Abhängigkeit abhängt und Sie diese Abhängigkeit aktualisieren müssen, muss jemand dafür sorgen, dass auch die Mock-Objekte, die die Abhängigkeit nachahmen, mit allen Änderungen aktualisiert werden: einschließlich solcher, die zu Inkompatibilitäten führen, dokumentierter und undokumentierter Änderungen (die sich auch auf unser System auswirken können). Dies wird besonders ärgerlich, wenn man eine Abhängigkeit aktualisieren möchte, die (nur mit Mocks arbeitende) Testsuite aber keine Gewissheit darüber bietet, dass alle getesteten Fälle garantiert funktionieren.</p></li><li><p>Es erfordert Disziplin, sicherzustellen, dass die Anstrengungen der Entwicklung und dem Testen des Systems und nicht den Mockups zugutekommen.</p></li></ul><p>Aus diesen Gründen plädieren viele dafür, genau den umgekehrten Weg zu gehen: niemals Mocks (oder Stubs usw.) zu verwenden, sondern sich ausschließlich auf reale Abhängigkeiten zu verlassen. Dieser Ansatz funktioniert sehr gut in Demos oder wenn das System winzig ist und nur wenige Testfälle eine große Testabdeckung erzeugen. Bei solchen Tests kann es sich um Integrationstests handeln (grob gesagt: die Überprüfung eines Teils eines Systems anhand realer Abhängigkeiten) oder um End-to-End-Tests (bei denen alle realen Abhängigkeiten gleichzeitig verwendet und das Verhalten des Systems an allen Enden überprüft wird, während Benutzer-Workflows simuliert werden, die das System als nutzbar und erfolgreich definieren). Ein klarer Vorteil dieses Ansatzes ist, dass wir (oft unbeabsichtigt) auch unsere Annahmen über die Abhängigkeiten und deren Integration in das System, an dem wir arbeiten, überprüfen.</p><p>Wenn Tests jedoch ausschließlich reale Abhängigkeiten verwenden, müssen wir folgende Aspekte berücksichtigen:</p><ul><li><p>Manche Testszenarien benötigen die eigentliche Abhängigkeit nicht (z. B. um die statischen Invarianten einer Anfrage zu überprüfen).</p></li><li><p>Solche Tests werden üblicherweise nicht als komplette Testreihen auf den Rechnern der Entwickler ausgeführt, da das Warten auf Feedback zu viel Zeit in Anspruch nehmen würde.</p></li><li><p>Sie benötigen mehr Ressourcen auf den CI-Maschinen, und es kann mehr Zeit in Anspruch nehmen, die Dinge so einzustellen, dass keine Zeit und Ressourcen verschwendet werden.</p></li><li><p>Es könnte sich als nicht trivial erweisen, Abhängigkeiten mit Testdaten zu initialisieren.</p></li><li><p>Tests mit realen Abhängigkeiten eignen sich hervorragend, um Code vor größeren Refaktorierungen, Migrationen oder Abhängigkeitsaktualisierungen abzugrenzen.</p></li><li><p>Es handelt sich dabei eher um undurchsichtige Tests, bei denen nicht detailliert auf die internen Abläufe des zu testenden Systems eingegangen wird, dafür aber auf die Ergebnisse geachtet wird.</p></li></ul><h2>Der optimale Punkt: Verwenden Sie beide Tests.</h2><p>Anstatt Ihr System nur mit einer Art von Test zu prüfen, können Sie, wo sinnvoll, auf beide Arten zurückgreifen und versuchen, die Nutzung beider zu verbessern.</p><ul><li><p>Führen Sie zuerst Mock-basierte Tests durch, da diese viel schneller sind, und erst wenn alle erfolgreich waren, führen Sie langsamere Abhängigkeitstests durch.</p></li><li><p>Wählen Sie Mocks für Szenarien, in denen externe Abhängigkeiten nicht wirklich benötigt werden: Wenn das Mocken zu viel Zeit in Anspruch nehmen würde, sollte der Code nur dafür massiv geändert werden; verlassen Sie sich auf externe Abhängigkeiten.</p></li><li><p>Es spricht nichts dagegen, einen Codeabschnitt mit beiden Ansätzen zu testen, solange es sinnvoll ist.</p></li></ul><h2>Beispiel für das zu testende System</h2><p>In den nächsten Abschnitten werden wir ein Beispiel verwenden, das Sie <a href="https://github.com/pioorg/testing-elasticsearch">hier</a> finden. Es handelt sich um eine winzige Demo-Anwendung, die in Java 21 geschrieben wurde, Maven als Build-Tool verwendet, auf dem Elasticsearch-Client basiert und die neueste Erweiterung von Elasticsearch nutzt, nämlich <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL</a> (die neue prozedurale Abfragesprache von Elastic). Auch wenn Java nicht Ihre Programmiersprache ist, sollten Sie die im Folgenden besprochenen Konzepte verstehen und auf Ihre Systemarchitektur übertragen können. Anhand eines realen Codebeispiels lassen sich manche Dinge einfach leichter erklären.</p><p>Die <code>BookSearcher</code> hilft uns bei der Suche und Analyse von Daten, in unserem Fall Büchern (wie in <a href="https://www.elastic.co/search-labs/blog/esql-queries-to-java-objects">einem der vorherigen Beiträge</a> gezeigt).</p><ul><li><p>Es benötigt Elasticsearch exakt in Version <code>8.15.x</code> als einzige Abhängigkeit (siehe <code>isCompatibleWithBackend()</code>), z. B. weil wir nicht sicher sind, ob unser Code vorwärtskompatibel ist, und sicher sind, dass er nicht rückwärtskompatibel ist. Bevor wir Elasticsearch in der Produktionsumgebung auf eine neuere Version aktualisieren, werden wir es zunächst in den Tests aktualisieren, um sicherzustellen, dass das Verhalten des zu testenden Systems unverändert bleibt.</p></li><li><p>Wir können es verwenden, um die Anzahl der in einem bestimmten Jahr veröffentlichten Bücher zu ermitteln (siehe <code>numberOfBooksPublishedInYear</code>).</p></li><li><p>Wir könnten es auch verwenden, wenn wir unseren Datensatz analysieren und die 20 meistveröffentlichten Autoren zwischen zwei gegebenen Jahren ermitteln müssen (siehe <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>Testen Sie zunächst mit Mock-Objekten.</h2><p>Für die Erstellung der in unseren Tests verwendeten Mocks werden wir <a href="https://site.mockito.org/">Mockito</a> verwenden, eine sehr beliebte Mocking-Bibliothek im Java-Ökosystem.</p><p>Wir könnten folgendermaßen vorgehen, um die Mock-Objekte vor jedem Test zurückzusetzen:</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>Wie bereits erwähnt, lässt sich nicht alles einfach mit Mocks testen. Aber manche Dinge können wir (und sollten wir wahrscheinlich sogar). Lassen Sie uns überprüfen, ob derzeit nur die Version <code>8.15.x</code> von Elasticsearch unterstützt wird (zukünftig werden wir den Bereich möglicherweise erweitern, sobald wir bestätigt haben, dass unser System mit zukünftigen Versionen kompatibel ist):</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>Wir können auf ähnliche Weise überprüfen (einfach durch die Rückgabe einer anderen Nebenversion), dass unsere <code>BookSearcher</code> noch nicht mit <code>8.16.x</code> funktionieren wird, da wir uns nicht sicher sind, ob sie damit kompatibel sein wird:</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>Schauen wir uns nun an, wie wir etwas Ähnliches erreichen können, wenn wir mit einem echten Elasticsearch testen. Hierfür verwenden wir <a href="https://java.testcontainers.org/modules/elasticsearch/">das Elasticsearch-Modul von Testcontainers</a>, das nur eine Voraussetzung hat: Es benötigt Zugriff auf Docker, da es Docker-Container für Sie ausführt. Aus einer bestimmten Perspektive ist Testcontainers einfach eine Möglichkeit, Docker-Container zu betreiben. Anstatt dies jedoch in Ihrem Docker Desktop (oder einem ähnlichen System), in Ihrer Befehlszeile oder in Skripten zu tun, können Sie Ihre Anforderungen in der Programmiersprache ausdrücken, die Sie kennen. Dadurch wird es möglich, Images abzurufen, Container zu starten, sie nach Tests automatisch zu löschen, Dateien hin und her zu kopieren, Befehle auszuführen, Protokolle zu untersuchen usw. – und zwar direkt aus Ihrem Testcode heraus.</p><p>Der Stub könnte folgendermaßen aussehen:</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>In diesem Beispiel verlassen wir uns auf <a href="https://java.testcontainers.org/test_framework_integration/junit_5/">die JUnit-Integration von Testcontainers</a> mit <code>@Testcontainers</code> und <code>@Container</code>, was bedeutet, dass wir uns keine Gedanken darüber machen müssen, Elasticsearch vor unseren Tests zu starten und danach zu stoppen. Wir müssen lediglich den Client vor jedem Test erstellen und ihn nach jedem Test wieder schließen (um Ressourcenlecks zu vermeiden, die sich auf größere Testsuiten auswirken könnten).</p><p>Die Annotation eines nicht-statischen Feldes mit <code>@Container</code> bedeutet, dass für jeden Test ein neuer Container gestartet wird, sodass wir uns keine Gedanken über veraltete Daten oder das Zurücksetzen des Containerzustands machen müssen. Bei vielen Tests könnte sich dieser Ansatz jedoch als ungeeignet erweisen. Daher werden wir ihn in einem der nächsten Beiträge mit Alternativen vergleichen.</p><p><strong>Notiz:</strong></p>Durch die Nutzung von <code>docker.elastic.co</code> (dem offiziellen Docker-Image-Repository von Elastic) vermeiden Sie, Ihre Limits auf Docker Hub zu überschreiten.

Es wird außerdem empfohlen, in der Test- und Produktionsumgebung dieselbe Version Ihrer Abhängigkeit zu verwenden, um maximale Kompatibilität zu gewährleisten. Wir empfehlen außerdem, bei der Auswahl der Version genau zu sein. Aus diesem Grund gibt es kein <code>latest</code> -Tag für Elasticsearch-Images.<h2>Verbindung zu Elasticsearch in Tests</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html">Der Elasticsearch Java-Client</a> kann auch dann eine Verbindung zu Elasticsearch herstellen, wenn in einem Testcontainer Sicherheitseinstellungen und SSL/TLS aktiviert sind (was bei Versionen 8.x Standard ist, weshalb wir in der Containerdeklaration nichts in Bezug auf die Sicherheit angeben mussten). Sofern bei der in der Produktion verwendeten Elasticsearch-Instanz ebenfalls TLS und einige Sicherheitsfunktionen aktiviert sind, empfiehlt es sich, den Integrationstest so nah wie möglich an das Produktionsszenario anzupassen und diese daher in den Tests nicht zu deaktivieren.</p><p>Wie man die für die Verbindung notwendigen Daten erhält, vorausgesetzt, der Container ist dem Feld oder der Variablen <code>elasticsearch</code> zugewiesen:</p><ul><li><p><code>elasticsearch.getHost()</code> wird Ihnen den Host anzeigen, auf dem der Container läuft (was in den meisten Fällen wahrscheinlich <code>"localhost"</code> sein wird, aber bitte legen Sie dies nicht fest im Code fest, da es je nach Ihrer Konfiguration manchmal einen anderen Namen haben kann; daher sollte der Host immer dynamisch ermittelt werden).</p></li><li><p><code>elasticsearch.getMappedPort(9200)</code> liefert den Host-Port, den Sie verwenden müssen, um eine Verbindung zu Elasticsearch herzustellen, das innerhalb des Containers läuft (da sich der externe Port bei jedem Start des Containers ändert, muss dies ebenfalls ein dynamischer Aufruf sein).</p></li><li><p>Sofern sie nicht überschrieben wurden, lauten der Standardbenutzername und das Standardpasswort <code>"elastic"</code> bzw. <code>"changeme"</code> .</p></li><li><p>Wenn bei der Container-Einrichtung kein SSL/TLS-Zertifikat angegeben wurde und die sichere Verbindung nicht deaktiviert ist (was ab Version 8.x das Standardverhalten ist), wird ein selbstsigniertes Zertifikat generiert. Dem zu vertrauen (z. B. wie <a href="https://curl.se/docs/manpage.html#--cacert">es cURL kann</a>) kann das Zertifikat mit <code>elasticsearch.caCertAsBytes()</code> (was <code>Optional&lt;byte[]&gt;</code> zurückgibt) abgerufen werden, oder eine andere bequeme Möglichkeit besteht darin, <code>SSLContext</code> mit <code>createSslContextFromCa()</code> zu erhalten.</p></li></ul><p>Das Gesamtergebnis könnte folgendermaßen aussehen:</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>Ein weiteres Beispiel für die Erstellung einer Instanz von <code>ElasticsearchClient</code> finden Sie im <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherIntTest.java#L61">Demo-Projekt</a>.</p><p><strong>Notiz</strong>:</p>Informationen zur Erstellung von Clients in Produktionsumgebungen finden Sie in <a href="https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/connecting.html#_verifying_https_with_a_certificate_fingerprint">der Dokumentation</a>.<h2>Erster Integrationstest</h2><p>Unser allererster Test, mit dem wir überprüfen, ob wir <code>BookSearcher</code> mit Elasticsearch Version 8.15.x erstellen können, könnte folgendermaßen aussehen:</p>@Test
void canCreateClientWithContainerRunning_8_15() {
    Assertions.assertDoesNotThrow(() -&gt; new BookSearcher(client));
}
<p>Wie Sie sehen, müssen wir nichts weiter einrichten. Wir müssen die von Elasticsearch zurückgegebene Version nicht simulieren. Wir müssen lediglich <code>BookSearcher</code> einen Client bereitstellen, der mit einer realen Elasticsearch-Instanz verbunden ist, die von Testcontainers für uns gestartet wurde.</p><h2>Integrationstests kümmern sich weniger um die Interna.</h2><p>Machen wir ein kleines Experiment: Nehmen wir an, wir müssen aufhören, Daten aus dem Ergebnissatz mithilfe von Spaltenindizes zu extrahieren, und uns stattdessen auf Spaltennamen verlassen. Also in der Methode <code>isCompatibleWithBackend</code> anstelle von</p>return rs.getInt(1) == 8 &amp;&amp; rs.getInt(2) == 15;
<p>Wir werden Folgendes haben:</p>return rs.getInt("major") == 8 &amp;&amp; rs.getInt("minor") == 15;
<p>Wenn wir beide Tests erneut ausführen, werden wir feststellen, dass der Integrationstest mit dem echten Elasticsearch weiterhin problemlos verläuft. Allerdings funktionierten die Tests mit Mocks nicht mehr, da wir Aufrufe wie <code>rs.getInt(int)</code> und nicht wie <code>rs.getInt(String)</code> simuliert hatten. Damit sie erfolgreich sind, müssen wir sie nun entweder simulieren oder beide simulieren, abhängig von anderen Anwendungsfällen in unserer Testsuite.</p><h2>Integrationstests können wie eine Kanone sein, die eine Fliege tötet.</h2><p>Integrationstests sind in der Lage, das Verhalten des Systems zu überprüfen, selbst wenn keine externen Abhängigkeiten benötigt werden. Allerdings ist die Verwendung auf diese Weise in der Regel eine Verschwendung von Ausführungszeit und Ressourcen. Betrachten wir die Methode <code>mostPublishedAuthorsInYears(int minYear, int maxYear)</code>. Die ersten beiden Zeilen lauten wie folgt:</p>assert minYear &lt;= maxYear;
String query = // here goes the query
<p>Die erste Anweisung prüft eine Bedingung, die in keiner Weise von Elasticsearch (oder irgendeiner anderen externen Abhängigkeit) abhängt. Daher ist es nicht nötig, irgendwelche Container zu starten, um lediglich zu überprüfen, ob eine Ausnahme ausgelöst wird, wenn <code>minYear</code> größer als <code>maxYear</code> ist.</p><p>Ein einfacher Mock-Test, der zudem schnell und ressourcenschonend ist, genügt vollkommen, um dies sicherzustellen. Nachdem wir die Mock-ups eingerichtet haben, können wir einfach Folgendes tun:</p>BookSearcher systemUnderTest = new BookSearcher(esClient);

Assertions.assertThrows(
    AssertionError.class,
    () -&gt; systemUnderTest.mostPublishedAuthorsInYears(2012, 2000)
);
<p>Das Starten einer Abhängigkeit anstatt des Mockens wäre in <a href="https://github.com/pioorg/testing-elasticsearch/blob/e800b4b2ab3d706efcafb9a8182480e69e475b86/src/test/java/testing_elasticsearch/BookSearcherMockingTest.java#L89">diesem Testfall</a> verschwenderisch, da es keine Möglichkeit gibt, einen sinnvollen Aufruf für diese Abhängigkeit durchzuführen.</p><p>Um jedoch das Verhalten ab <code>String query = ...</code> zu überprüfen, ob die Abfrage korrekt geschrieben ist, werden die erwarteten Ergebnisse geliefert: Die Clientbibliothek ist in der Lage, korrekte Anfragen und Antworten zu senden, es gibt keine Syntaxänderungen, und daher ist es viel einfacher, einen Integrationstest zu verwenden, z. B.:</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>Auf diese Weise können wir sicher sein, dass unsere Abfrage uns genau das liefert, was wir erwarten, wenn wir unsere Daten an Elasticsearch senden (in dieser oder einer zukünftigen Version, zu der wir migrieren): Das Datenformat hat sich nicht geändert, die Abfrage ist weiterhin gültig und die gesamte Middleware (Clients, Treiber, Sicherheit usw.) funktioniert weiterhin. Wir müssen uns keine Gedanken darüber machen, die Mockups aktuell zu halten; die einzige Änderung, die erforderlich ist, um die Kompatibilität mit z. B. <code>8.15</code> würde dies ändern:</p>static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.15.0";
<p>Dasselbe passiert, wenn Sie sich beispielsweise entscheiden, Verwenden Sie das gute alte QueryDSL anstelle von ES|QL: Die Ergebnisse der Abfrage (unabhängig von der Sprache) sollten immer noch die gleichen sein.</p><h2>Verwenden Sie bei Bedarf beide Ansätze.</h2><p>Das Beispiel der Methode <code>mostPublishedAuthorsInYears</code> veranschaulicht, dass eine einzelne Methode mit beiden Methoden getestet werden kann. Und vielleicht sollte es das sogar sein.</p><ul><li><p>Die ausschließliche Verwendung von Mock-Objekten bedeutet, dass wir die Mock-Objekte pflegen müssen und keinerlei Vertrauen in die Systemaktualisierung haben.</p></li><li><p>Die ausschließliche Verwendung von Integrationstests würde bedeuten, dass wir eine Menge Ressourcen verschwenden, die wir überhaupt nicht benötigen.</p></li></ul><h2>Fassen wir zusammen</h2><ul><li><p>Die Verwendung von Mocking- und Integrationstests mit Elasticsearch ist möglich.</p></li><li><p>Verwenden Sie Mocking-Tests wie fast-detection-net und starten Sie Tests mit Abhängigkeiten erst, wenn diese erfolgreich durchlaufen werden (z. B. mit <code>./mvnw test '-Dtest=!TestInt*' &amp;&amp; ./mvnw test '-Dtest=TestInt*'</code> oder den Plugins <a href="https://maven.apache.org/surefire/maven-failsafe-plugin/">Failsafe</a> und <a href="https://maven.apache.org/surefire/maven-surefire-plugin/">Surefire</a> ).</p></li><li><p>Verwenden Sie Mocks, wenn Sie das Verhalten Ihres Systems testen ("Codezeilen"), bei denen die Integration mit externen Abhängigkeiten keine Rolle spielt (oder sogar übersprungen werden kann).</p></li><li><p>Nutzen Sie Integrationstests, um Ihre Annahmen über die Integration mit externen Systemen zu überprüfen.</p></li><li><p>Scheuen Sie sich nicht, beide Ansätze auszuprobieren – sofern dies gemäß den oben genannten Punkten sinnvoll ist.</p></li></ul><p>Man könnte anmerken, dass es übertrieben ist, die Version (in unserem Fall <code>8.15.x</code>) so streng zu handhaben. Die alleinige Verwendung des Versionskennzeichens wäre möglich, aber bitte beachten Sie, dass es in diesem Beitrag stellvertretend für alle anderen Funktionen steht, die sich zwischen den Versionen ändern könnten.</p><p>Im <a href="https://www.elastic.co/search-labs/blog/automated-integration-tests-faster-elasticsearch">nächsten Teil dieser Serie</a> werden wir uns mit Möglichkeiten zur Initialisierung von Elasticsearch in einem Testcontainer mit Testdatensätzen befassen. Teilt uns mit, ob ihr etwas auf Basis dieses Blogs gebaut habt oder ob ihr Fragen in unseren <a href="https://discuss.elastic.co/">Diskussionsforen</a> und <a href="https://communityinviter.com/apps/elasticstack/elastic-community">im Community-Slack-Kanal</a> habt.</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[Wir stellen LangChain4j vor, um die LLM-Integration in Java-Anwendungen zu vereinfachen.]]></title>
    <description><![CDATA[LangChain4j (LangChain für Java) ist ein leistungsstarkes Toolset, mit dem Sie Ihre RAG-Anwendung in reinem Java erstellen können.]]></description>
    <content:encoded><![CDATA[<p>Das <a href="https://docs.langchain4j.dev/">LangChain4j-Framework</a> wurde 2023 mit <a href="https://github.com/langchain4j/langchain4j/blob/main/README.md#introduction">folgendem Ziel</a> entwickelt:</p>Das Ziel von LangChain4j ist es, die Integration von LLMs in Java-Anwendungen zu vereinfachen.<p>LangChain4j bietet eine standardisierte Methode, um:</p><ul><li><p>Einbettungen (Vektoren) aus einem gegebenen Inhalt erstellen, beispielsweise aus einem Text</p></li><li><p>Einbettungen in einem Einbettungsspeicher speichern</p></li><li><p>Suche nach ähnlichen Vektoren im Einbettungsspeicher</p></li><li><p>mit LLMs sprechen</p></li><li><p>Nutzen Sie die Chat-Speicherfunktion, um sich an den Kontext einer Diskussion mit einem LLM zu erinnern.</p></li></ul><p>Diese Liste ist nicht vollständig, und die LangChain4j-Community implementiert ständig neue Funktionen.</p><p>Dieser Beitrag behandelt die ersten Hauptbestandteile des Frameworks.</p><h2>Hinzufügen von LangChain4j OpenAI zu unserem Projekt</h2><p>Wie bei allen Java-Projekten ist es einfach eine Frage der Abhängigkeiten. Hier verwenden wir Maven, aber das gleiche Ergebnis ließe sich auch mit jedem anderen Abhängigkeitsmanager erzielen.</p><p>Als ersten Schritt für das Projekt, das wir hier aufbauen wollen, werden wir OpenAI verwenden, daher müssen wir lediglich das <code>langchain4j-open-ai</code> -Artefakt hinzufügen:</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>Für den restlichen Code verwenden wir entweder unseren eigenen API-Schlüssel, den Sie durch die Registrierung eines Kontos bei <a href="https://platform.openai.com/signup/">OpenAI</a> erhalten, oder den vom LangChain4j-Projekt bereitgestellten Schlüssel, der jedoch nur zu Demonstrationszwecken dient:</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>Wir können nun eine Instanz unseres ChatLanguageModel erstellen:</p>ChatLanguageModel model = OpenAiChatModel.withApiKey(getOpenAiApiKey());
<p>Und schließlich können wir eine einfache Frage stellen und die Antwort erhalten:</p>String answer = model.generate("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);
<p>Die gegebene Antwort könnte etwa so lauten:</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>Wenn Sie diesen Code ausführen möchten, sehen Sie sich bitte die Klasse <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step1AiChatTest.java">Step1AiChatTest.java</a> an.</p><h2>Mehr Kontext mit langchain4j</h2><p>Fügen wir das <code>langchain4j</code> -Artefakt hinzu:</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>Dieses Tool bietet uns einen Werkzeugkasten, der uns dabei helfen kann, eine fortgeschrittenere LLM-Integration für unseren Assistenten zu entwickeln. Hier erstellen wir einfach eine <code>Assistant</code> -Schnittstelle, die die <code>chat</code> -Methode bereitstellt, welche automatisch die zuvor definierte <code>ChatLanguageModel</code> aufruft:</p>interface Assistant {
  String chat(String userMessage);
}
<p>Wir müssen lediglich die Klasse LangChain4j <code>AiServices</code> bitten, eine Instanz für uns zu erstellen:</p>Assistant assistant = AiServices.create(Assistant.class, model);
<p>Und rufen Sie dann die Methode <code>chat(String)</code> auf:</p>String answer = assistant.chat("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);
<p>Das System verhält sich genauso wie zuvor. Warum also haben wir den Code geändert? Erstens ist es eleganter, aber darüber hinaus können Sie dem LLM nun mithilfe einfacher Annotationen Anweisungen geben:</p>interface Assistant {
  @SystemMessage("Please answer in a funny way.")
  String chat(String userMessage);
}
<p>Dies ergibt nun Folgendes:</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>Wenn Sie diesen Code ausführen möchten, sehen Sie sich bitte die Klasse <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step2AssistantTest.java">Step2AssistantTest.java</a> an.</p><h2>Wechsel zu einem anderen LLM: langchain4j-ollama</h2><p>Wir können das großartige <a href="https://ollama.com/">Ollama-Projekt</a> nutzen. Es ist hilfreich, einen LLM lokal auf Ihrem Rechner auszuführen.</p><p>Fügen wir das <code>langchain4j-ollama</code> -Artefakt hinzu:</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>Da wir den Beispielcode mithilfe von Tests ausführen, fügen wir unserem Projekt <a href="https://java.testcontainers.org/">Testcontainers</a> hinzu:</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>Wir können jetzt Docker-Container starten/stoppen:</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>Wir müssen lediglich das <code>model</code> -Objekt in ein <code>OllamaChatModel</code> ändern, anstatt des zuvor verwendeten <code>OpenAiChatModel</code> :</p>OllamaChatModel model = OllamaChatModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();
<p>Beachten Sie, dass das Abrufen des Bildes mit dem zugehörigen Modell einige Zeit in Anspruch nehmen kann, aber nach einer Weile erhalten Sie die Antwort:</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>Besser mit Speicher</h2><p>Wenn wir mehrere Fragen stellen, merkt sich das System standardmäßig nicht die vorherigen Fragen und Antworten. Wenn wir also nach der ersten Frage „Wann wurde er geboren?“ fragen, Unsere Anwendung wird folgende Fragen beantworten:</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>Das ist Unsinn. Stattdessen sollten wir <a href="https://docs.langchain4j.dev/tutorials/chat-memory">Chat Memory</a> verwenden:</p>ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
Assistant assistant = AiServices.builder(Assistant.class)
  .chatLanguageModel(model)
  .chatMemory(chatMemory)
  .build();
<p>Die erneute Beantwortung derselben Fragen liefert nun ein aussagekräftiges Ergebnis:</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>Fazit</h2><p>Im <a href="https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store">nächsten Beitrag</a> werden wir herausfinden, wie wir mithilfe von Elasticsearch als Einbettungsspeicher Fragen an unseren privaten Datensatz stellen können. Das wird uns die Möglichkeit geben, unsere Anwendungssuche auf die nächste Stufe zu heben.</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[KI]]></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>