<?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[Javascript - 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[Javascript - 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/javascript-programming</link>
    </image>
    <link>https://www.elastic.co/de/search-labs/blog/category/javascript-programming</link>
    <atom:link href="https://www.elastic.co/de/search-labs/rss/category/javascript-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[de]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 10:46:26 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Entwicklung eines agentenbasierten RAG-Assistenten mit JavaScript und Mastra und Elasticsearch]]></title>
    <description><![CDATA[Lerne, wie du KI-Agenten im JavaScript-Ökosystem entwickelst.]]></description>
    <content:encoded><![CDATA[<p>Diese Idee kam mir mitten in einer hitzigen Fantasy-Basketball-Liga mit hohem Einsatz. Ich fragte mich: <em>Könnte ich einen KI-Agenten entwickeln, der mir hilft, meine wöchentlichen Begegnungen zu dominieren? Absolut!</em></p><p>In diesem Beitrag zeigen wir Ihnen, wie Sie mit <a href="https://mastra.ai/en/docs">Mastra</a> einen agentenbasierten RAG-Assistenten und eine leichtgewichtige JavaScript-Webanwendung zur Interaktion mit diesem erstellen. Durch die Anbindung dieses Agenten an Elasticsearch erhalten Sie Zugriff auf strukturierte Spielerdaten und die Möglichkeit, statistische Aggregationen in Echtzeit durchzuführen, um Ihnen auf Spielerstatistiken basierende Empfehlungen geben zu können. Besuchen Sie das GitHub- <a href="https://github.com/jdarmada/nba-ai-assistant-js.git">Repository</a> , um die Schritte nachzuvollziehen; die <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/README.md">README-Datei</a> enthält Anweisungen zum Klonen und Ausführen der Anwendung. </p><p>So sollte es aussehen, wenn alles zusammengebaut ist:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63ea3e7a09306fbf/6a17f1d97f6f150e22c09c50/1c73bd1dc1b5fe54f025c7a2b7c322acc9122f3a-1999x1393.png" alt="" /><p>Hinweis: Dieser Blogbeitrag baut auf „ <a href="https://www.elastic.co/search-labs/blog/ai-agents-ai-sdk-elasticsearch">Entwicklung von KI-Agenten mit AI SDK und Elastic</a>“ auf. Wenn Sie sich mit KI-Agenten im Allgemeinen noch nicht auskennen und nicht wissen, wofür sie eingesetzt werden könnten, fangen Sie dort an.
</p><h2><strong>Architekturübersicht</strong></h2><p>Kernstück des Systems ist ein großes Sprachmodell (LLM), das als Denkmaschine (das Gehirn) des Agenten fungiert. Es interpretiert die Benutzereingaben, entscheidet, welche Tools aufgerufen werden sollen, und koordiniert die notwendigen Schritte, um eine relevante Antwort zu generieren.</p><p>Der Agent selbst wird von Mastra, einem Agenten-Framework im JavaScript-Ökosystem, bereitgestellt. Mastra umschließt das LLM mit einer Backend-Infrastruktur, stellt es als API-Endpunkt bereit und bietet eine Schnittstelle zur Definition von Tools, Systemaufforderungen und Agentenverhalten.</p><p>Im Frontend verwenden wir <a href="https://vite.dev/guide/">Vite</a> , um schnell eine React-Webanwendung zu erstellen, die eine Chat-Oberfläche zum Senden von Anfragen an den Agenten und zum Empfangen seiner Antworten bereitstellt.</p><p>Schließlich gibt es noch Elasticsearch, das Spielerstatistiken und Matchup-Daten speichert, die der Agent abfragen und aggregieren kann.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte13f09493f217047/6a17f1db1d1b83178d93e546/443bdc00d84ed1dd49e9f9e431e86ca4b0892563-1999x977.png" alt="" /><h2><strong>Hintergrund</strong></h2><p>Lassen Sie uns einige grundlegende Konzepte durchgehen:</p><h3><strong>Was ist agentic RAG?</strong></h3><p>KI-Agenten können mit anderen Systemen interagieren, unabhängig agieren und Aktionen auf Basis ihrer definierten Parameter ausführen. Agentic RAG kombiniert die Autonomie eines KI-Agenten mit den Prinzipien der abrufverstärkten Generierung und ermöglicht es einem LLM, auszuwählen, welche Werkzeuge aufgerufen und welche Daten als Kontext verwendet werden sollen, um eine Antwort zu generieren. Lesen Sie <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">hier</a> mehr über RAG.</p><h3><strong>Warum sollte man bei der Wahl eines Frameworks über das AI-SDK hinausgehen?</strong></h3><p>Es gibt viele KI-Agenten-Frameworks, und Sie haben wahrscheinlich schon von den bekannteren wie <a href="https://www.elastic.co/search-labs/blog/using-crewai-with-elasticsearch">CrewAI</a>, <a href="https://www.elastic.co/search-labs/blog/using-autogen-with-elasticsearch">AutoGen</a> und <a href="https://www.elastic.co/search-labs/blog/build-rag-workflow-langgraph-elasticsearch">LangGraph</a> gehört. Die meisten dieser Frameworks verfügen über eine gemeinsame Funktionspalette, darunter die Unterstützung verschiedener Modelle, die Verwendung von Werkzeugen und das Speichermanagement.</p><p>Hier finden Sie eine <a href="https://docs.google.com/spreadsheets/d/1B37VxTBuGLeTSPVWtz7UMsCdtXrqV5hCjWkbHN8tfAo/edit?gid=0#gid=0">Vergleichstabelle</a> der verschiedenen Frameworks von Harrison Chase (CEO von LangChain).</p><p>Was mich an Mastra besonders interessiert hat, ist, dass es sich um ein JavaScript-basiertes Framework handelt, das für Full-Stack-Entwickler entwickelt wurde, um Agenten einfach in ihr Ökosystem zu integrieren. Das AI-SDK von Vercel kann das meiste davon auch, aber Mastra glänzt vor allem dann, wenn Ihre Projekte komplexere Agenten-Workflows beinhalten. Mastra erweitert die vom AI-SDK festgelegten Basismuster, und in diesem Projekt werden wir sie gemeinsam verwenden.</p><h3><strong>Rahmenwerke und Überlegungen zur Modellwahl</strong></h3><p>Diese Frameworks können zwar dabei helfen, schnell KI-Agenten zu entwickeln, es gibt jedoch einige Nachteile zu beachten. Wenn man beispielsweise andere Frameworks als KI-Agenten oder Abstraktionsschichten im Allgemeinen verwendet, verliert man ein Stück weit die Kontrolle. Wenn das LLM die Werkzeuge nicht korrekt verwendet oder etwas tut, was Sie nicht möchten, erschwert die Abstraktion die Fehlersuche. Dennoch ist dieser Kompromiss meiner Meinung nach den Komfort und die Geschwindigkeit wert, die man beim Erstellen von Projekten erhält, insbesondere weil diese Frameworks immer beliebter werden und ständig weiterentwickelt werden.</p><p>Auch diese Frameworks sind modellagnostisch, das heißt, Sie können verschiedene Modelle einsetzen und verwenden. Bedenken Sie jedoch, dass sich die Modelle in den Datensätzen unterscheiden, mit denen sie trainiert wurden, und dass sich dadurch auch die Ergebnisse unterscheiden. Manche Modelle unterstützen nicht einmal den Aufruf von Tools. Es ist also möglich, zwischen verschiedenen Modellen zu wechseln und diese auszuprobieren, um zu sehen, welches die besten Ergebnisse liefert. Bedenken Sie jedoch, dass Sie höchstwahrscheinlich für jedes Modell die Systemabfrage neu schreiben müssen. Zum Beispiel mit Llama3.3 Im Vergleich zu GPT-4o sind deutlich mehr Aufforderungen und spezifische Anweisungen erforderlich, um die gewünschte Antwort zu erhalten.</p><h3><strong>NBA Fantasy Basketball</strong></h3><p>Beim Fantasy-Basketball geht es darum, mit einer Gruppe von Freunden eine Liga zu gründen (Achtung: Je nachdem, wie wettbewerbsorientiert eure Gruppe ist, könnte dies den Status eurer Freundschaften beeinträchtigen), wobei in der Regel auch Geld auf dem Spiel steht. Jeder von euch stellt dann ein Team aus 10 Spielern zusammen, die abwechselnd wöchentlich gegen die 10 Spieler eines anderen Freundes antreten. Die Punkte, die zu Ihrer Gesamtpunktzahl beitragen, ergeben sich aus der Leistung jedes Ihrer Spieler gegen seine Gegner in einer bestimmten Woche.</p><p>Wenn ein Spieler Ihres Teams verletzt, gesperrt usw. wird, steht Ihnen eine Liste von Free Agents zur Verfügung, die Sie Ihrem Team hinzufügen können. Hier findet ein Großteil des schwierigen Denkens im Fantasy-Sport statt, denn man hat nur eine begrenzte Anzahl an Spielern zur Auswahl und jeder ist ständig auf der Suche nach dem besten Spieler.</p><p>Hier wird unser NBA-KI-Assistent seine Stärken ausspielen, insbesondere in Situationen, in denen Sie schnell entscheiden müssen, welchen Spieler Sie auswählen. Anstatt manuell nachschlagen zu müssen, wie ein Spieler gegen einen bestimmten Gegner abschneidet, kann der Assistent diese Daten schnell finden und Durchschnittswerte vergleichen, um Ihnen eine fundierte Empfehlung zu geben.</p><p>Nachdem Sie nun einige Grundlagen zu Agentic RAG und NBA Fantasy Basketball kennengelernt haben, sehen wir uns das Ganze mal in der Praxis an.</p><h2><strong>Aufbau des Projekts</strong></h2><p>Falls Sie an irgendeiner Stelle nicht weiterkommen oder es nicht von Grund auf neu erstellen möchten, schauen Sie bitte im <a href="https://github.com/jdarmada/nba-ai-assistant-js.git">Repository</a> nach.</p><h3><strong>Was wir behandeln werden</strong></h3><ol><li><p><strong>Gerüstbau für das Projekt:</strong></p><ol><li><p><strong>Backend (Mastra):</strong> Verwenden Sie npx create mastra@latest, um das Backend zu erstellen und die Agentenlogik zu definieren.</p></li><li><p><strong>Frontend (Vite + React):</strong> Verwenden Sie npm create vite@latest, um die Frontend-Chat-Oberfläche für die Interaktion mit dem Agenten zu erstellen.</p></li></ol></li><li><p><strong>Einrichten von Umgebungsvariablen</strong></p><ol><li><p>Installieren Sie dotenv, um Umgebungsvariablen zu verwalten.</p></li><li><p>Erstellen Sie eine .env-Datei Datei und geben Sie die erforderlichen Variablen an.</p></li></ol></li><li><p><strong>Elasticsearch einrichten</strong></p><ol><li><p>Erstellen Sie einen Elasticsearch-Cluster (entweder lokal oder in der Cloud).</p></li><li><p>Installieren Sie den offiziellen Elasticsearch-Client.</p></li><li><p>Stellen Sie sicher, dass Umgebungsvariablen zugänglich sind.</p></li><li><p>Verbindung zum Client herstellen.</p></li></ol></li><li><p><strong>Massenhaftes Einlesen von NBA-Daten in Elasticsearch</strong></p><ol><li><p>Erstellen Sie einen Index mit den entsprechenden Zuordnungen, um Aggregationen zu ermöglichen.</p></li><li><p>Spielerstatistiken aus einer CSV-Datei massenhaft in einen Elasticsearch-Index importieren.</p></li></ol></li><li><p><strong>Elasticsearch-Aggregationen definieren</strong></p><ol><li><p>Abfrage zur Berechnung historischer Durchschnittswerte gegen einen bestimmten Gegner.</p></li><li><p>Abfrage zur Berechnung der Saison-Durchschnittswerte gegen einen bestimmten Gegner.</p></li></ol></li><li><p><strong>Datei zum Spielervergleich</strong></p><ol><li><p>Konsolidiert Hilfsfunktionen und Elasticsearch-Aggregationen.</p></li></ol></li><li><p><strong>Agentenaufbau</strong></p><ol><li><p>Fügen Sie die Agentendefinition und die Systemeingabeaufforderung hinzu.</p></li><li><p>Installieren Sie zod und definieren Sie Tools.</p></li><li><p>Fügen Sie eine Middleware-Konfiguration zur Behandlung von CORS hinzu.</p></li></ol></li><li><p><strong>Integration des Frontends</strong></p><ol><li><p>Die Interaktion mit dem Agenten erfolgt über die useChat-Funktion des AI-SDK.</p></li><li><p>Erstellen Sie eine Benutzeroberfläche, die ordnungsgemäß formatierte Konversationen ermöglicht.</p></li></ol></li><li><p><strong>Die Anwendung ausführen</strong></p><ol><li><p>Starten Sie sowohl das Backend (Mastra-Server) als auch das Frontend (React-App).</p></li><li><p>Beispielabfragen und Anwendungsbeispiele.</p></li></ol></li><li><p><strong>Was kommt als Nächstes: Den Agenten intelligenter machen.</strong></p><ol><li><p>Durch die Integration semantischer Suchfunktionen können aussagekräftigere Empfehlungen ermöglicht werden.</p></li><li><p>Aktivieren Sie dynamische Abfragen, indem Sie die Suchlogik auf den Elasticsearch MCP (Model Context Protocol)-Server verlagern.</p></li></ol></li></ol><h3><strong>Voraussetzungen</strong></h3><ul><li><p><strong>Node.js und npm</strong>: Sowohl das Backend als auch das Frontend laufen auf Node.js. Stellen Sie sicher, dass Sie Node 18+ und npm v9+ installiert haben (das in Node 18+ standardmäßig enthalten ist).</p></li><li><p><strong>Elasticsearch-Cluster:</strong> Ein aktiver Elasticsearch-Cluster, entweder lokal oder in der Cloud.</p></li><li><p><strong>OpenAI API-Schlüssel</strong>: Generieren Sie einen auf der Seite „API-Schlüssel“ im <a href="https://platform.openai.com/api-keys">Entwicklerportal von OpenAI</a>.</p></li></ul><p></p><h3><strong>Projektstruktur</strong></h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt749baa120552e4ab/6a17f1dd1d1b83bfe993e54a/1c0bde11ad0eead523a95e03b9b905aa776e3fd1-1420x934.png" alt="" /><h4><strong>Schritt 1: Das Projektgerüst erstellen</strong></h4><ol><li><p>Erstellen Sie zunächst das Verzeichnis nba-ai-assistant-js und navigieren Sie darin mit folgendem Befehl: </p></li></ol>mkdir nba-ai-assistant-js &amp;&amp; cd nba-ai-assistant-js<p><strong>Backend:</strong></p><ol><li><p>Verwenden Sie das Mastra-Erstellungstool mit folgendem Befehl: </p></li></ol>npx create-mastra@latest<p>2. Sie sollten nun einige Eingabeaufforderungen in Ihrem Terminal erhalten. Bei der ersten nennen wir das Projekt-Backend:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65abf68fe588e968/6a17f1de63baff2814741d5b/de2725031ed6837db99a979efcdd0ece1e197dbb-608x84.png" alt="" /><p>3. Als nächstes behalten wir die Standardstruktur für die Speicherung der Mastra-Dateien bei, also Eingabe <code>src/</code>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89bd829fcf0ae6b9/6a17f1e04b055dd30e432302/88919d9ff1852126395e1fcd700ecb1b59aac63c-866x116.png" alt="" /><p>4. Dann wählen wir OpenAI als unseren Standard-LLM-Anbieter.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd167cc77a40b9a8/6a17f1e11480099e29b48863/2328761e769f3ded134e5a21e8a0bf8f41e88f68-404x210.png" alt="" /><p>5. Abschließend werden Sie nach Ihrem OpenAI-API-Schlüssel gefragt. Fürs Erste wählen wir die Option, dies zu überspringen und es später in einer<code> .env</code> -Datei bereitzustellen.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12654151ed495370/6a17f1e22f4a5c0f84fa89f9/0662de9bd28758e377e4c63df8d08b479068ce63-444x120.png" alt="" /><p><strong>Frontend:</strong></p><ol><li><p>Wechseln Sie zurück in das Stammverzeichnis und führen Sie das <a href="https://vite.dev/guide/">Vite-Erstellungstool</a> mit folgendem Befehl aus: <code>npm create vite@latest frontend -- --template react</code></p></li></ol><p>Dadurch sollte eine leichtgewichtige React-App mit dem Namen <code>frontend</code> und einer spezifischen Vorlage für React erstellt werden.</p><p>Wenn alles gut geht, sollten Sie in Ihrem Projektverzeichnis ein Backend-Verzeichnis sehen, das den Mastra-Code enthält, und ein <code>frontend</code> -Verzeichnis mit Ihrer React-App.</p><p></p><h4><strong>Schritt 2: Umgebungsvariablen einrichten</strong></h4><ol><li><p>Zur Verwaltung sensibler Schlüssel verwenden wir das Paket <code>dotenv</code> , um unsere Umgebungsvariablen aus der .env-Datei zu laden. Datei. Navigieren Sie zum Backend-Verzeichnis und installieren Sie <code>dotenv</code>:</p></li></ol>cd backend
npm install dotenv --save<p>2. Im Backend-Verzeichnis befindet sich eine example.env-Datei mit den entsprechenden Variablen zum Ausfüllen. Wenn Sie Ihre eigene Version erstellen, achten Sie darauf, die folgenden Variablen einzubeziehen:</p># OpenAI Configuration
OPENAI_API_KEY=your_openai_api_key_here

# Elasticsearch Configuration
ELASTIC_ENDPOINT=your_elasticsearch_endpoint_here
ELASTIC_API_KEY=your_elasticsearch_api_key_here
<p></p><p>Hinweis: Stellen Sie sicher, dass diese Datei von Ihrer Versionskontrolle ausgeschlossen wird, indem Sie <code>.env</code> zu <code>.gitignore</code> hinzufügen.</p><h4><strong>Schritt 3: Elasticsearch einrichten</strong></h4><p>Als Erstes benötigen Sie einen aktiven Elasticsearch-Cluster. Es gibt zwei Möglichkeiten:</p><ul><li><p><strong>Option A: Elasticsearch Cloud verwenden</strong></p><ul><li><p>Registrieren Sie sich für <a href="https://cloud.elastic.co/registration">Elastic Cloud</a></p></li><li><p>Erstellen Sie eine neue Bereitstellung</p></li><li><p>Rufen Sie Ihre Endpunkt-URL und Ihren API-Schlüssel (kodiert) ab.</p></li></ul></li><li><p><strong>Option B: Elasticsearch lokal ausführen</strong></p><ul><li><p>Elasticsearch lokal installieren und ausführen</p></li><li><p>Verwenden Sie http://localhost:9200 als Endpunkt.</p></li><li><p>API-Schlüssel generieren</p></li></ul></li></ul><p></p><p><strong>Installation des Elasticsearch-Clients auf dem Backend:</strong></p><ol><li><p>Installieren Sie zunächst den offiziellen Elasticsearch-Client in Ihrem Backend-Verzeichnis:</p></li></ol>npm install @elastic/elasticsearch<p>2. Erstellen Sie anschließend ein Verzeichnis „lib“, um wiederverwendbare Funktionen zu speichern, und wechseln Sie in dieses Verzeichnis:</p>mkdir lib &amp;&amp; cd lib<p>3. Erstellen Sie darin eine neue Datei namens <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticClient.js">elasticClient.js</a>. Diese Datei initialisiert den Elasticsearch-Client und stellt ihn für die Verwendung in Ihrem gesamten Projekt zur Verfügung.</p><p>4. Da wir ECMAScript-Module (ESM) verwenden, sind __dirname and __-Dateinamen nicht verfügbar. Um sicherzustellen, dass Ihre Umgebungsvariablen korrekt aus der .env-Datei geladen werden Fügen Sie diese Konfiguration am Anfang der Datei im Backend-Ordner hinzu:</p>import { config } from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { Client } from '@elastic/elasticsearch';

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

// Load environment variables from the correct path
config({ path: envPath });<p>5. Initialisieren Sie nun den Elasticsearch-Client mithilfe Ihrer Umgebungsvariablen und überprüfen Sie die Verbindung:</p>//Elastic client Initialization, make sure environment variables are being loaded in correctly
const config= {
    node: `${process.env.ELASTIC_ENDPOINT}`,
    auth: {
        apiKey: `${process.env.ELASTIC_API_KEY}`,
    },
};

export const elasticClient = new Client(config);

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

checkConnection();
<p>Nun können wir diese Clientinstanz in jede Datei importieren, die mit Ihrem Elasticsearch-Cluster interagieren muss.</p><p></p><h4><strong>Schritt 4: Massenhaftes Einlesen von NBA-Daten in Elasticsearch</strong></h4><p><strong>Datensatz:</strong></p><p>Für dieses Projekt werden wir auf die Datensätze im Verzeichnis <a href="https://github.com/jdarmada/nba-ai-assistant-js/tree/main/backend">backend/data</a> des Repos zurückgreifen. Unser NBA-Assistent wird diese Daten als Wissensbasis für statistische Vergleiche und die Generierung von Empfehlungen nutzen.</p><ul><li><p><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/sample_nba_data.csv">sample_player_game_stats.csv</a> - Beispielhafte Spielstatistiken eines Spielers (z. B. Punkte, Rebounds, Steals usw.) pro Spiel und Spieler während seiner gesamten NBA-Karriere. Wir werden diesen Datensatz für Aggregationen verwenden. (Hinweis: Dies sind Beispieldaten, die zu Demonstrationszwecken generiert wurden und nicht aus offiziellen NBA-Quellen stammen.)</p></li><li><p><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/playerAndTeamInfo.js">playerAndTeamInfo.js</a> - Ersetzt die Spieler- und Team-Metadaten, die normalerweise über einen API-Aufruf bereitgestellt werden, damit der Agent Spieler- und Teamnamen IDs zuordnen kann. Da wir Beispieldaten verwenden, möchten wir den Aufwand des Abrufens von einer externen API vermeiden. Daher haben wir einige Werte fest codiert, auf die der Agent zugreifen kann.</p></li></ul><p></p><p><strong>Durchführung:</strong></p><ol><li><p>Erstellen Sie im Verzeichnis <code>backend/lib</code> eine Datei mit dem Namen <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/playerDataIngestion.js">playerDataIngestion.js</a>.</p></li><li><p>Importe einrichten, den CSV-Dateipfad auflösen und das Parsen einrichten. Da wir ESM verwenden, müssen wir <code>__dirname</code> rekonstruieren, um den Pfad zur Beispiel-CSV-Datei aufzulösen. Außerdem importieren wir <a href="http://node.js/">Node.js.</a> eingebaute Module <code>fs</code> und <code>readline</code>, um die gegebene CSV-Datei Zeile für Zeile zu analysieren.</p></li></ol>import fs from 'fs';
import readline from 'readline';
import path from 'path';
import { fileURLToPath } from 'url';
import { elasticClient } from './elasticClient.js';

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

//Since we are using ES modules __dirname and __filename don't exist, so this is a workaround that allows us to use the absolute file path for our sample data.
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const filePath = path.resolve(__dirname, '../data/sample_nba_data.csv');<p>Damit sind Sie bestens gerüstet, um die CSV-Datei effizient zu lesen und zu analysieren, wenn wir zum Schritt der Massenverarbeitung übergehen.</p><p>3. Erstellen Sie einen Index mit der entsprechenden Zuordnung. Obwohl Elasticsearch Feldtypen mithilfe von <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dynamic">Dynamic Mapping</a> automatisch ableiten kann, möchten wir hier explizit vorgehen, damit jede Statistik als numerisches Feld behandelt wird. Dies ist wichtig, da wir diese Felder später für Aggregationen verwenden werden. Wir möchten außerdem den Typ <code>float </code>für Statistiken wie Punkte, Rebounds usw. verwenden, um sicherzustellen, dass wir Dezimalwerte einbeziehen. Zum Schluss möchten wir die Mapping-Eigenschaft <code>dynamic: 'strict'</code> hinzufügen, damit Elasticsearch nicht dynamisch nicht erkannte Felder zuordnet. 
</p>// Function to create an index with mappings
async function createIndex() {
    try {
        // Check if the index already exists
        const exists = await elasticClient.indices.exists({ index: indexName });

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

        console.log('Index created:', response);
        return true;
    } catch (error) {
        console.error('Error creating index:', error);
        return false;
    }
}
<p>4. Fügen Sie die Funktion hinzu, um die CSV-Daten in Ihren Elasticsearch-Index zu importieren. Innerhalb des Codeblocks überspringen wir die Kopfzeile. Anschließend werden die einzelnen Zeilen durch Kommas getrennt und in das Dokumentobjekt eingefügt. Dieser Schritt reinigt sie außerdem und stellt sicher, dass es sich um den richtigen Typ handelt. Als nächstes fügen wir die Dokumente zusammen mit den Indexinformationen in das bulkBody-Array ein, welches als Nutzlast für die Massenaufnahme in Elasticsearch dient.</p>async function bulkIngestCsv(filePath) {
    const readStream = fs.createReadStream(filePath);
    const rl = readline.createInterface({
        input: readStream,
        crlfDelay: Infinity,
    });

    const bulkBody = [];
    let lineNum = 0;

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

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

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

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

    console.log(`Parsed ${lineNum} lines from CSV`);
<p>5. Anschließend können wir <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">die Bulk-API</a> von Elasticsearch mit <code>elasticClient.bulk()</code> verwenden, um mehrere Dokumente in einer einzigen Anfrage zu erfassen. Die unten beschriebene Fehlerbehandlung ist so aufgebaut, dass sie Ihnen eine Übersicht darüber gibt, wie viele Dokumente nicht eingelesen werden konnten und wie viele erfolgreich eingelesen wurden.</p>try {
        // Perform the bulk request
        const response = await elasticClient.bulk({ body: bulkBody });

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

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

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

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

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

    } catch (error) {
        console.error('Error performing bulk ingestion:', error);
    }
}
<p>6. Führen Sie die unten stehende Funktion <code>main()</code> aus, um die Funktionen <code>createIndex()</code> und <code>bulkIngestCsv()</code> nacheinander auszuführen.</p>// Run this function
async function main() {
    const result = await createIndex();
    if (!result) {
        console.error('Index setup failed. Aborting.');
        return;
    }

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

main();
<p>Wenn in der Konsole eine Meldung erscheint, dass die Massenaufnahme erfolgreich war, überprüfen Sie kurz Ihren Elasticsearch-Index, um festzustellen, ob die Dokumente tatsächlich erfolgreich aufgenommen wurden.</p><h4><strong>Schritt 5: Elasticsearch-Aggregationen definieren und konsolidieren</strong></h4><p>Dies sind die Hauptfunktionen, die wir verwenden werden, wenn wir die Werkzeuge für den KI-Agenten definieren, um die Statistiken der Spieler miteinander zu vergleichen.</p><p>1. Navigieren Sie zum Verzeichnis <code>backend/lib</code> und erstellen Sie eine Datei namens <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticAggs.js">elasticAggs.js</a>.</p><p>2. Fügen Sie die unten stehende Abfrage hinzu, um historische Durchschnittswerte für einen Spieler gegen einen bestimmten Gegner zu berechnen. Diese Abfrage verwendet einen <code>bool</code> <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/full-text-search/filters">-Filter</a> mit 2 Bedingungen: eine, die <code>player_id</code> entspricht, und eine weitere, die <code>opponent_team_id</code> entspricht, um nur die relevanten Spiele abzurufen. Wir müssen keine Dokumente zurückgeben, uns geht es nur um die Aggregationen, deshalb setzen wir <code>size:0</code>. Im Block <code>aggs</code> führen wir mehrere <a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">Metrikaggregationen</a> parallel auf Feldern wie <code>points, rebounds, assists, steals, blocks</code> und <code>fg_percentage</code> durch, um deren Durchschnittswerte zu berechnen. LLMs können bei Berechnungen ungenau sein, und dies lagert diesen Prozess an Elasticsearch aus, um sicherzustellen, dass unser NBA-KI-Assistent Zugriff auf genaue Daten hat.</p>export async function getHistoricalAveragesAgainstOpponent(player_id, opponent_team_id) {
    try {
        //Query for Historical Averages
        const historicalQuery = await elasticClient.search({
            index: 'sample-nba-player-data', 
            size: 0,
            query: {
                bool: {
                    must: [
                        {
                            term: {
                                player_id: {
                                    value: player_id,
                                },
                            },
                        },
                        {
                            term: {
                                opponent_team_id: {
                                    value: opponent_team_id,
                                },
                            },
                        },
                    ],
                },
            },
            aggs: {
                avg_points: { avg: { field: 'points' } },
                avg_rebounds: { avg: { field: 'rebounds' } },
                avg_assists: { avg: { field: 'assists' } },
                avg_steals: { avg: { field: 'steals' } },
                avg_blocks: { avg: { field: 'blocks' } },
             avg_fg_percentage: { avg: { field: 'fg_percentage' } },
            },
        });

        return {
            points: historicalQuery.aggregations.avg_points.value || 0,
            rebounds: historicalQuery.aggregations.avg_rebounds.value || 0,
            assists: historicalQuery.aggregations.avg_assists.value || 0,
            steals: historicalQuery.aggregations.avg_steals.value || 0,
            blocks: historicalQuery.aggregations.avg_blocks.value || 0,
            fgPercentage: historicalQuery.aggregations.avg_fg_percentage.value || 0,
        };
    } catch (error) {
        console.error('Query error from getHistoricalAveragesAgainstOpponent function:', error);
        return { error: 'Queries failed in getting historical averages against opponent.' };
    }
}
<p>3. Um die Saisondurchschnittswerte eines Spielers gegen einen bestimmten Gegner zu berechnen, verwenden wir praktisch die gleiche Abfrage wie für die historischen Ergebnisse. Der einzige Unterschied bei dieser Abfrage besteht darin, dass der <code>bool</code> -Filter eine zusätzliche Bedingung für <code>game_date</code> enthält. Das Feld <code>game_date</code> muss innerhalb des Bereichs der aktuellen NBA-Saison liegen. In diesem Fall liegt der Bereich zwischen <code>2024-10-01</code> und <code>2025-06-30</code>. Die unten stehende zusätzliche Bedingung stellt sicher, dass die nachfolgenden Aggregationen nur die Spiele dieser Saison berücksichtigen.
</p>        {
                            range: {
                    //Range for this season, change to match current season
                                game_date: {
                                    gte: '2024-10-01',
                                    lte: '2025-06-30',
                                },
                            },
<h4><strong>Schritt 6: Spielervergleichstool</strong></h4><p>Um unseren Code modular und wartbar zu halten, erstellen wir eine Hilfsdatei, die Metadaten-Hilfsfunktionen und Elasticsearch-Aggregationen zusammenfasst. Dies wird das Hauptwerkzeug des Agenten mit Strom versorgen. Mehr dazu später:</p><p>1. Erstellen Sie eine neue Datei <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/comparePlayers.js">comparePlayers.js</a> im Verzeichnis <code>backend/lib</code> .</p><p>2. Fügen Sie die unten stehende Funktion hinzu, um Metadaten-Helfer und Elasticsearch-Aggregationslogik in einer einzigen Funktion zu konsolidieren, die das Hauptwerkzeug des Agenten antreibt.
</p>import { playersByName } from '../data/playerAndTeamInfo.js';
import { teamsByName } from '../data/playerAndTeamInfo.js';
import { upcomingMatchups } from '../data/playerAndTeamInfo.js';
import { getHistoricalAveragesAgainstOpponent } from './elasticAggs.js';
import { getSeasonAveragesAgainstOpponent } from './elasticAggs.js';

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

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

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

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

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

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

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

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

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

    return [player1, player2];
}
<h4><strong>Schritt 7: Erstellung des Agenten</strong></h4><p>Nachdem Sie nun das Frontend- und Backend-Gerüst erstellt, NBA-Spieldaten eingespielt und eine Verbindung zu Elasticsearch hergestellt haben, können wir damit beginnen, alle Teile zusammenzufügen, um den Agenten zu entwickeln.</p><p><strong>Definition des Agenten</strong></p><p>1. Navigieren Sie zur Datei <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/agents/index.ts">index.ts</a> im Verzeichnis <code>backend/src/mastra/agents</code> und fügen Sie die Agentendefinition hinzu. Sie können Felder wie die folgenden angeben:</p><ul><li><p><strong>Name:</strong> Geben Sie Ihrem Agenten einen Namen, der als Referenz verwendet wird, wenn er im Frontend aufgerufen wird.</p></li><li><p><strong>Anweisungen/Systemaufforderung: </strong>Eine Systemaufforderung gibt dem LLM den anfänglichen Kontext und die Regeln vor, die während der Interaktion zu befolgen sind. Es ähnelt der Aufforderung, die Benutzer über das Chatfenster senden, diese wird jedoch vor jeglicher Benutzereingabe angezeigt. Auch dies hängt vom gewählten Modell ab.</p></li><li><p><strong>Modell:</strong> Welches LLM soll verwendet werden (Mastra unterstützt OpenAI, Anthropic, lokale Modelle usw.)?</p></li><li><p><strong>Tools:</strong> Eine Liste der Tool-Funktionen, die der Agent aufrufen kann.</p></li><li><p><strong>Speicher:</strong> (Optional) Wenn der Agent sich den Gesprächsverlauf usw. merken soll. Der Einfachheit halber können wir ohne persistenten Speicher beginnen, obwohl Mastra diesen unterstützt.</p></li></ul><p></p>import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { playerComparisonTool } from '../tools';

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

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

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

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

      Format your response using Markdown syntax. Use:

        Example output format:

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


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

        #### Fantasy Recommendation  
        Explain which player is the better fantasy pickup and why.
      
    `,
    model: openai('gpt-4o'),
    tools: { playerComparisonTool },
});
<p><strong>
Werkzeuge definieren</strong></p><ol><li><p>Navigieren Sie zur Datei <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/tools/index.ts">index.ts</a> im Verzeichnis <code>backend/src/mastra/tools</code> .</p></li><li><p>Installieren Sie Zod mit folgendem Befehl:</p></li></ol>npm install zod<p>3. Werkzeugdefinitionen hinzufügen. Beachten Sie, dass wir die Funktion innerhalb der Datei <code>comparePlayers.js</code> als Hauptfunktion importieren, die der Agent beim Aufruf dieses Tools verwenden wird. Mit der Funktion <code>createTool()</code> von Mastra registrieren wir unser <code>playerComparisonTool</code>. Zu den Feldern gehören:</p><ul><li><p><code>id</code>Dies ist eine Beschreibung in natürlicher Sprache, die dem Agenten hilft zu verstehen, was das Tool leistet.</p></li><li><p><code>input schema</code>Um die Form der Eingabe für das Tool zu definieren, verwendet Mastra das <a href="https://zod.dev/">Zod</a> -Schema, eine TypeScript-Bibliothek zur Schema-Validierung. Zod hilft dabei, indem es sicherstellt, dass der Agent korrekt strukturierte Eingaben vornimmt und die Ausführung des Tools verhindert, wenn die Eingabestruktur nicht übereinstimmt.</p></li><li><p><code>description</code>Dies ist eine Beschreibung in natürlicher Sprache, die dem Agenten helfen soll zu verstehen, wann er anrufen und das Tool verwenden soll.</p></li><li><p><code>execute</code>Die Logik, die beim Aufruf des Tools ausgeführt wird. In unserem Fall verwenden wir eine importierte Hilfsfunktion, um Leistungsstatistiken zurückzugeben.</p></li></ul>import { comparePlayersForNextMatchup } from '../../../lib/comparePlayers.js'
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const playerComparisonTool = createTool({
    id: "Compare two NBA players",
    inputSchema: z.object({
        player1:z.string(),
        player2:z.string()
    }),
    description: "Use this tool to compare two players given in the user prompt.",
    execute: async ({ context: { player1, player2 } }) =&gt; {
        return await comparePlayersForNextMatchup(player1, player2);
      },
})<p><strong>Hinzufügen von Middleware zur Behandlung von CORS</strong></p><p>Fügen Sie Middleware auf dem Mastra-Server hinzu, um <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">CORS</a> zu behandeln. Man sagt, es gäbe drei Dinge im Leben, denen man nicht entkommen kann: den Tod, die Steuern und für Webentwickler zusätzlich CORS. Kurz gesagt, Cross-Origin Resource Sharing ist eine Browser-Sicherheitsfunktion, die verhindert, dass das Frontend Anfragen an ein Backend sendet, das auf einer anderen Domain oder einem anderen Port läuft. Obwohl wir sowohl das Backend als auch das Frontend auf localhost betreiben, verwenden sie unterschiedliche Ports, wodurch die CORS-Richtlinie ausgelöst wird. Wir müssen die in der <a href="https://mastra.ai/en/docs/server-db/middleware">Mastra-Dokumentation</a> beschriebene Middleware hinzufügen, damit unser Backend diese Anfragen vom Frontend zulässt.</p><p>1. Navigieren Sie zur Datei <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/index.ts">index.ts</a> im Verzeichnis <code>backend/src/mastra</code> und fügen Sie die Konfiguration für CORS hinzu:</p><ul><li><p><code>origin: ['http://localhost:5173']</code></p><ul><li><p>Erlaubt Anfragen nur von dieser Adresse (Vite-Standardadresse)</p></li></ul></li><li><p><code>allowMethods: ["GET", "POST"]</code></p><ul><li><p>Zulässige HTTP-Methoden. In den meisten Fällen wird POST verwendet.</p></li></ul></li><li><p><code>allowHeaders: ["Content-Type", "Authorization", "x-mastra-client-type, "x-highlight-request", "traceparent"],</code></p><ul><li><p>Diese legen fest, welche benutzerdefinierten Header in Anfragen verwendet werden können.</p></li></ul></li></ul><p></p>import { Mastra } from '@mastra/core/mastra';
import { basketballAgent } from './agents';

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

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

});

console.log('Mastra server configured.'); // Log after server configuration
<h4><strong>Schritt 8: Integration des Frontends</strong></h4><p>Diese React-Komponente stellt eine einfache Chat-Oberfläche bereit, die über den <a href="https://mastra.ai/en/docs/frameworks/agentic-uis/ai-sdk#using-the-usechat-hook">useChat()</a> -Hook aus <code>@ai-sdk/react</code> eine Verbindung zum Mastra AI-Agenten herstellt. Wir werden diesen Hook auch verwenden, um die Tokenverwendung und Toolaufrufe anzuzeigen sowie die Konversation darzustellen. In der obigen Systemabfrage bitten wir den Agenten außerdem, die Antwort im Markdown-Format auszugeben. Daher verwenden wir <code>react-markdown</code> , um die Antwort korrekt zu formatieren.</p><p></p><p>1. Installieren Sie im Frontend-Verzeichnis das Paket @ai-sdk/react, um den useChat()-Hook verwenden zu können.</p>npm install @ai-sdk/react<p>2. Installieren Sie im selben Verzeichnis React Markdown, damit wir die vom Agenten generierte Antwort richtig formatieren können.</p>npm install react-markdown<p>3. Implementiere <code>useChat()</code>. Dieser Hook steuert die Interaktion zwischen Ihrem Frontend und Ihrem KI-Agenten-Backend. Es verarbeitet Nachrichtenstatus, Benutzereingaben und Statusinformationen und bietet Lebenszyklus-Hooks zur Überwachung. Zu den Optionen, die wir übergeben, gehören:</p><ul><li><p><code>api:</code> Dies definiert den Endpunkt Ihres Mastra AI-Agenten. Standardmäßig wird Port 4111 verwendet, und wir möchten außerdem die Route hinzufügen, die Streaming-Antworten unterstützt.</p></li><li><p><code>onToolCall</code>Dies wird immer dann ausgeführt, wenn der Agent ein Tool aufruft; wir verwenden es, um zu verfolgen, welche Tools unser Agent aufruft.</p></li><li><p><code>onFinish</code>Dies wird ausgeführt, nachdem der Agent eine vollständige Antwort abgegeben hat. Auch wenn wir Streaming aktiviert haben, wird <code>onFinish</code> erst ausgeführt, nachdem die gesamte Nachricht empfangen wurde, und nicht nach jedem einzelnen Datenblock. Hier verwenden wir es, um unsere Token-Nutzung zu verfolgen. Dies kann hilfreich sein, um die LLM-Kosten zu überwachen und zu optimieren.</p></li></ul><p>4. Zum Schluss gehen Sie zur Komponente <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/frontend/components/ChatUI.jsx">ChatUI.jsx</a> im Verzeichnis <code>frontend/components</code> , um die Benutzeroberfläche für unsere Konversation zu erstellen. Als Nächstes muss die Antwort in eine <code>ReactMarkdown</code> -Komponente eingeschlossen werden, um die Antwort des Agenten korrekt zu formatieren.</p>import React, { useState } from 'react';
import { useChat } from '@ai-sdk/react';
import ReactMarkdown from 'react-markdown';

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

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

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

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

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

    return (
        &lt;div&gt;
            &lt;div className="agent-info"&gt;
                &lt;h4 className="stats-title"&gt;What's My Agent Doing?&lt;/h4&gt;

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

                    &lt;div className="usage-stats"&gt;
                        &lt;p&gt;Prompt Token Usage: {promptTokenUsage}&lt;/p&gt;
                        &lt;p&gt;Completion Token Usage: {completionTokenUsage}&lt;/p&gt;
                        &lt;p&gt;Total Token Usage: {totalTokenUsage}&lt;/p&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;

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

            &lt;form onSubmit={handleSubmit}&gt;
                &lt;input
                    type="text"
                    value={input}
                    onChange={handleInputChange}
                    placeholder="Input two players you want to compare."
                    className="input-box"
                /&gt;
                &lt;button type="submit" disabled={status === 'streaming'}&gt;
                    {status === 'streaming' ? 'Thinking...' : 'Send'}
                &lt;/button&gt;
            &lt;/form&gt;
        &lt;/div&gt;
    );
}<h4><strong>Schritt 9: Ausführen der Anwendung</strong></h4><p>Glückwunsch! Sie können die Anwendung nun ausführen. Folgen Sie diesen Schritten, um sowohl das Backend als auch das Frontend zu starten.</p><ol><li><p>Öffnen Sie ein Terminalfenster, beginnend im Stammverzeichnis, und navigieren Sie zum Backend-Verzeichnis. Starten Sie dort den Mastra-Server:</p></li></ol>cd backend

npm run dev<p>2. Öffnen Sie ein weiteres Terminalfenster, beginnend mit dem Stammverzeichnis, und navigieren Sie zum Frontend-Verzeichnis. Starten Sie dort die React-App:</p><p></p>cd frontend

npm run dev<p></p><p>3. Öffnen Sie Ihren Browser und navigieren Sie zu:</p><p></p><p><a href="http://localhost:5173/">http://localhost:5173</a></p><p></p><p>Sie sollten die Chat-Oberfläche sehen können. Probieren Sie diese Beispielaufforderungen aus:</p><ul><li><p>„Vergleiche LeBron James und Stephen Curry“</p></li><li><p>"Wen soll ich wählen, Jayson Tatum oder Luka Doncic?"</p></li></ul><p></p><h3><strong>Was kommt als Nächstes: Den Agenten intelligenter machen.</strong></h3><p>Um den Assistenten handlungsfähiger und die Empfehlungen aussagekräftiger zu gestalten, werde ich in der nächsten Version einige wichtige Verbesserungen vornehmen.</p><p></p><p><strong>Semantische Suche nach NBA-Nachrichten</strong></p><p>Es gibt unzählige Faktoren, die die Leistung eines Spielers beeinflussen können, viele davon spiegeln sich nicht in den reinen Statistiken wider. Dinge wie Verletzungsberichte, Aufstellungsänderungen oder auch eine Spielanalyse nach dem Spiel findet man nur in Nachrichtenartikeln. Um diesen zusätzlichen Kontext zu erfassen, werde ich semantische Suchfunktionen hinzufügen, damit der Agent relevante NBA-Artikel abrufen und diese Erzählung in seine Empfehlungen einbeziehen kann.</p><p></p><p><strong>Dynamische Suche mit dem Elasticsearch MCP-Server</strong></p><p>MCP (Model Context Protocol) entwickelt sich schnell zum Standard für die Art und Weise, wie Agenten Verbindungen zu Datenquellen herstellen. Ich werde die Suchlogik auf den Elasticsearch MCP-Server migrieren, wodurch der Agent Abfragen dynamisch erstellen kann, anstatt sich auf vordefinierte Suchfunktionen zu verlassen, die wir bereitstellen. Dies ermöglicht uns die Nutzung von Workflows in natürlicher Sprache und reduziert den Aufwand, jede einzelne Suchanfrage manuell zu formulieren. Erfahren Sie <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">hier</a> mehr über den Elasticsearch MCP-Server und den aktuellen Stand des Ökosystems.</p><p></p><p>Diese Änderungen sind bereits im Gange, bleiben Sie gespannt!</p><h3><strong>Fazit</strong></h3><p></p><p>In diesem Blog haben wir einen agentenbasierten RAG-Assistenten entwickelt, der mithilfe von JavaScript, Mastra und Elasticsearch maßgeschneiderte Empfehlungen für Ihr Fantasy-Basketballteam liefert. Wir behandelten Folgendes:</p><ul><li><p><strong>Grundlagen von Agentic RAG</strong> und wie die Kombination der Autonomie eines KI-Agenten mit den Werkzeugen zur effektiven Nutzung von RAG zu differenzierteren und dynamischeren Agenten führen kann.</p></li><li><p><strong>Elasticsearch </strong>und wie seine Datenspeicherfunktionen und leistungsstarken nativen Aggregationen es zu einem großartigen Partner als Wissensbasis für ein LLM machen.</p></li><li><p><strong>Das Mastra- </strong>Framework und wie es die Entwicklung dieser Agenten für Entwickler im JavaScript-Ökosystem vereinfacht.</p></li></ul><p>Egal, ob Sie ein Basketballfanatiker sind, sich damit beschäftigen, wie man KI-Agenten entwickelt, oder beides wie ich, ich hoffe, dieser Blog hat Ihnen einige Bausteine für den Einstieg geliefert. Das vollständige Repository ist auf <a href="https://github.com/jdarmada/nba-ai-assistant-js">GitHub</a> verfügbar, Sie können es gerne klonen und damit experimentieren. Jetzt hol dir den Sieg in der Fantasy-Liga!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agentic-rag</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agentic-rag</guid>
    <category><![CDATA[KI]]></category>
    <category><![CDATA[Agentische KI]]></category>
    <category><![CDATA[Javascript]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ffd561836a4cb20/6a17f1e47b54f978588b39e4/8132ed781c1ea5d46ca244182f421ed5c721f23b-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch in JavaScript richtig anwenden, Teil II]]></title>
    <description><![CDATA[Erfahren Sie mehr über Best Practices für die Produktion und darüber, wie Sie den Elasticsearch Node.js-Client in serverlosen Umgebungen ausführen können, um Programmierfehler zu vermeiden. ]]></description>
    <content:encoded><![CDATA[<p>Dies ist der zweite Teil unserer Elasticsearch-in-JavaScript-Serie. Im<a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i"> ersten Teil haben wir gelernt,</a> wie wir unsere Umgebung richtig einrichten, den Node.js-Client konfigurieren, Daten indizieren und suchen. Im zweiten Teil lernen wir, wie man Best Practices für die Produktion implementiert und den Elasticsearch <a href="http://node.js">Node.js</a> -Client in serverlosen Umgebungen ausführt.</p><p>Wir werden Folgendes überprüfen:</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#production-best-practices">Best Practices für die Produktion</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#error-handling">Fehlerbehandlung</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#testing">Tests</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#serverless-environments">Serverlose Umgebungen</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#running-the-client-on-elastic-serverless">Den Client auf Elastic Serverless ausführen</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#running-the-client-on-function-as-a-service-environment">Ausführen des Clients in einer Function-as-a-Service-Umgebung</a></p></li></ul></li></ul><p><em>Den Quellcode mit den Beispielen finden Sie </em><a href="https://github.com/Delacrobix/JS-client-best-practices_article"><em><strong>hier</strong></em></a><em><strong>.</strong></em></p><h2>Best Practices für die Produktion</h2><h3>Fehlerbehandlung in Elasticsearch</h3><p>Ein nützliches Feature des Elasticsearch-Clients in Node.js ist, dass er Objekte für die möglichen Fehler in Elasticsearch bereitstellt, sodass man diese auf verschiedene Weise validieren und behandeln kann.</p><p>Um <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript/connecting#client-error-handling">sie alle zu sehen</a>, führen Sie folgenden Befehl aus: </p>const { errors } = require('@elastic/elasticsearch')
console.log(errors)<p>Kehren wir zum Suchbeispiel zurück und behandeln wir einige der möglichen Fehler:</p>app.get("/search/lexic", async (req, res) =&gt; {
 ....
  } catch (error) {
    if (error instanceof errors.ResponseError) {
      let errorMessage =
        "Response error!, query malformed or server down, contact the administrator!";

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

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

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});<p><code>ResponseError</code> Dies tritt insbesondere dann auf, wenn die Antwort <code>4xx</code> oder <code>5xx</code> lautet, was bedeutet, dass die Anfrage fehlerhaft ist oder der Server nicht verfügbar ist.</p><p>Wir können diese Art von Fehler testen, indem wir fehlerhafte Abfragen generieren, beispielsweise den Versuch, <strong>eine Termabfrage für ein Textfeld durchzuführen:</strong></p><p>Standardfehler:</p> {
    "success": false,
    "results": null,
    "error": "parsing_exception\n\tRoot causes:\n\t\tparsing_exception: [terms] query does not support [visit_details]"
}<p>Benutzerdefinierter Fehler: </p>{
    "erroStatus": 400,
    "success": false,
    "results": null,
    "error": "Response error!, query malformed or server down; contact the administrator!"
}<p>Wir können auch jeden Fehlertyp auf eine bestimmte Weise erfassen und behandeln. Zum Beispiel können wir in einem <code>TimeoutError</code> eine Wiederholungslogik hinzufügen.</p>app.get("/search/semantic", async (req, res) =&gt; {
    try {
  ...
  } catch (error) {
    if (error instanceof errors.TimeoutError) {


     // Retry logic...

      res.status(error.meta.statusCode).json({
        erroStatus: error.meta.statusCode,
        success: false,
        results: null,
        error:
          "The request took more than 10s after 3 retries. Try again later.",
      });
    }
  }
});<h3>Tests</h3><p>Tests sind entscheidend, um die Stabilität der App zu gewährleisten. Um den Code auf eine von Elasticsearch isolierte Weise zu testen, können wir die Bibliothek <a href="https://github.com/elastic/elasticsearch-js-mock">elasticsearch-js-mock</a> beim Erstellen unseres Clusters verwenden.</p><p>Mithilfe dieser Bibliothek können wir einen Client instanziieren, der dem realen Client sehr ähnlich ist, aber auf unsere Konfiguration reagiert, indem wir lediglich die HTTP-Schicht des Clients durch eine simulierte Schicht ersetzen, während der Rest mit dem Original übereinstimmt.</p><p>Wir werden die Mock-Bibliothek und <a href="https://github.com/avajs/ava">AVA</a> für automatisierte Tests installieren.</p><p><code>npm install @elastic/elasticsearch-mock</code></p><p><code>npm install --save-dev ava</code></p><p>Wir werden die Datei <code>package.json</code> so konfigurieren, dass die Tests ausgeführt werden. Stellen Sie sicher, dass es so aussieht:</p>"type": "module",
	"scripts": {
		"test": "ava"
	},
	"devDependencies": {
		"ava": "^5.0.0"
	}<p>Erstellen wir nun eine <code>test.js</code> -Datei und installieren wir unseren Mock-Client:</p>const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')

const mock = new Mock()
const client = new Client({
  node: 'http://localhost:9200',
  Connection: mock.getConnection()
})<p>Fügen Sie nun ein Mock-Objekt für die semantische Suche hinzu:</p>function createSemanticSearchMock(query, indexName) {
  mock.add(
    {
      method: "POST",
      path: `/${indexName}/_search`,
      body: {
        query: {
          semantic: {
            field: "semantic_field",
            query: query,
          },
        },
      },
    },
    () =&gt; {
      return {
        hits: {
          total: { value: 2, relation: "eq" },
          hits: [
            {
              _id: "1",
              _score: 0.9,
              _source: {
                owner_name: "Alice Johnson",
                pet_name: "Buddy",
                species: "Dog",
                breed: "Golden Retriever",
                vaccination_history: ["Rabies", "Parvovirus", "Distemper"],
                visit_details:
                  "Annual check-up and nail trimming. Healthy and active.",
              },
            },
            {
              _id: "2",
              _score: 0.7,
              _source: {
                owner_name: "Daniel Kim",
                pet_name: "Mochi",
                species: "Rabbit",
                breed: "Mixed",
                vaccination_history: [],
                visit_details:
                  "Nail trimming and general health check. No issues.",
              },
            },
          ],
        },
      };
    }
  );
}<p>Wir können nun einen Test für unseren Code erstellen, um sicherzustellen, dass der Elasticsearch-Teil immer die gleichen Ergebnisse liefert:</p>import test from 'ava';

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

  createSemanticSearchMock(query, indexName);

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

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

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

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

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

  if (result.results.length &gt; 0) {
    t.true(
      "_source" in result.results[0],
      "Each result must have a _source property"
    );
    t.true(
      "pet_name" in result.results[0]._source,
      "Results must include the pet_name field"
    );
    t.true(
      "visit_details" in result.results[0]._source,
      "Results must include the visit_details field"
    );
  }
});<p>Lasst uns die Tests durchführen.</p><p><code>npm run test</code></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt36304e286146f362/6a170559d7c02237b2de638f/42feae845ae8eae03c37ad7ad114e8db35984812-1186x302.png" alt="" /><p>Erledigt! Ab sofort können wir unsere App testen und uns dabei zu 100 % auf den Code konzentrieren, ohne uns von externen Faktoren beeinflussen zu lassen.</p><h2>Serverlose Umgebungen</h2><h3>Wie Sie den Client auf Elastic Serverless ausführen</h3><p>Wir haben die Ausführung von Elasticsearch in der Cloud oder lokal behandelt; der Node.js-Client unterstützt jedoch auch Verbindungen zu <a href="https://www.elastic.co/guide/en/serverless/current/intro.html">Elastic Cloud Serverless</a>.</p><p>Mit Elastic Cloud Serverless können Sie ein Projekt erstellen, bei dem Sie sich keine Gedanken um die Infrastruktur machen müssen, da Elastic dies intern übernimmt. Sie müssen sich nur um die Daten kümmern, die Sie indizieren möchten, und darum, wie lange Sie darauf zugreifen möchten.</p><p>Aus Nutzersicht entkoppelt Serverless Rechenleistung von Speicher und bietet automatische Skalierungsfunktionen sowohl für <a href="https://www.elastic.co/search-labs/blog/elasticsearch-serverless-tier-autoscaling">die Suche</a> als auch <a href="https://www.elastic.co/search-labs/blog/elasticsearch-ingest-autoscaling">für die Indizierung</a>. Dadurch können Sie nur die Ressourcen anbauen, die Sie tatsächlich benötigen.</p><p>Der Client nimmt folgende Anpassungen vor, um eine Verbindung zu Serverless herzustellen:</p><ul><li><p>Deaktiviert das Sniffing und ignoriert alle Sniffing-bezogenen Optionen.</p></li><li><p>Ignoriert alle in der Konfiguration übergebenen Knoten außer dem ersten und ignoriert jegliche Knotenfilter- und Auswahloptionen.</p></li><li><p>Aktiviert Komprimierung und `TLSv1_2_method` (genau wie bei der Konfiguration für Elastic Cloud).</p></li><li><p>Fügt allen Anfragen einen HTTP-Header `elastic-api-version` hinzu</p></li><li><p>Verwendet standardmäßig `CloudConnectionPool` anstelle von `WeightedConnectionPool`.</p></li><li><p>Deaktiviert die vom Anbieter bereitgestellten `content-type`- und `accept`-Header zugunsten der Standard-MIME-Typen.</p></li></ul><p>Um Ihr serverloses Projekt zu verbinden, müssen Sie den Parameter serverMode: serverless verwenden.</p>const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  node: 'ELASTICSEARCH_ENDPOINT',
  auth: { apiKey: 'ELASTICSEARCH_API_KEY' },
  serverMode: "serverless",
});<h3>Wie Sie den Client in einer Function-as-a-Service-Umgebung ausführen</h3><p>In diesem Beispiel haben wir einen Node.js-Server verwendet, aber Sie können auch über eine Function-as-a-Service-Umgebung mit Funktionen wie AWS Lambda, GCP Run usw. eine Verbindung herstellen.</p>'use strict'

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

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

exports.handler = async function (event, context) {
  // use the client
}<p>Ein weiteres Beispiel ist die Anbindung an Dienste wie Vercel, das ebenfalls serverlos ist. Sie können sich dieses <a href="https://github.com/elastic/elasticsearch-js/blob/main/docs/examples/proxy/README.md">vollständige Beispiel</a> ansehen, wie das geht, aber der relevanteste Teil des <a href="https://github.com/elastic/elasticsearch-js/blob/main/docs/examples/proxy/api/search.js">Suchendpunkts</a> sieht folgendermaßen aus:</p>const response = await client.search(
  {
    index: INDEX,
    // You could directly send from the browser
    // the Elasticsearch's query DSL, but it will
    // expose you to the risk that a malicious user
    // could overload your cluster by crafting
    // expensive queries.
    query: {
      match: { field: req.body.text },
    },
  },
  {
    headers: {
      Authorization: `ApiKey ${token}`,
    },
  }
);<p>Dieser Endpunkt befindet sich im Ordner /api und wird serverseitig ausgeführt, sodass der Client nur über den Parameter „text“ verfügt, der dem Suchbegriff entspricht.</p><p>Die Konsequenz aus der Verwendung von Function-as-a-Service ist, dass Funktionen, im Gegensatz zu einem Server, der rund um die Uhr läuft, nur die Maschine starten, auf der die Funktion ausgeführt wird, und dass die Maschine nach Beendigung der Funktion in den Ruhemodus wechselt, um weniger Ressourcen zu verbrauchen.</p><p>Diese Konfiguration kann praktisch sein, wenn die Anwendung nicht zu viele Anfragen erhält; andernfalls können die Kosten hoch sein. Sie müssen auch den <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html">Lebenszyklus der Funktionen</a> und die Laufzeiten berücksichtigen (die in manchen Fällen nur Sekunden betragen können).</p><h2>Fazit</h2><p>In diesem Artikel haben wir gelernt, wie man mit Fehlern umgeht, was in Produktionsumgebungen von entscheidender Bedeutung ist. Wir haben auch das Testen unserer Anwendung unter Verwendung von Mocking für den Elasticsearch-Dienst behandelt. Dies ermöglicht zuverlässige Tests unabhängig vom Zustand des Clusters und erlaubt es uns, uns auf unseren Code zu konzentrieren.</p><p>Zum Schluss demonstrierten wir, wie man einen vollständig serverlosen Stack einrichtet, indem man sowohl Elastic Cloud Serverless als auch eine Vercel-Anwendung bereitstellt.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii</guid>
    <category><![CDATA[Javascript]]></category>
    <category><![CDATA[Grundlagen]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc58be329ffebcd60/6a17043e47d49c0bc62d88ab/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png" length="0" type="image/png"/>
    <pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch in JavaScript richtig anwenden, Teil 1]]></title>
    <description><![CDATA[Hier wird erklärt, wie man ein produktionsreifes Elasticsearch-Backend in JavaScript erstellt.  

Erkunden Sie, wie Sie Elasticsearch mit JavaScript verwenden, um einen Server mit verschiedenen Such-Endpunkten zu erstellen, der Elasticsearch-Dokumente gemäß den Best Practices für Client/Server abfragt.]]></description>
    <content:encoded><![CDATA[<p>Dies ist der erste Artikel einer Reihe, die die Verwendung von Elasticsearch mit JavaScript behandelt. In dieser Reihe lernen Sie die Grundlagen der Verwendung von Elasticsearch in einer JavaScript-Umgebung kennen und erhalten einen Überblick über die wichtigsten Funktionen und Best Practices zur Erstellung einer Such-App. Am Ende dieses Kurses werden Sie alles wissen, was Sie benötigen, um Elasticsearch mit JavaScript auszuführen.</p><p>In diesem ersten Teil werden wir Folgendes besprechen:</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#environment">Umfeld</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#frontend,-backend,-or-serverless?">Frontend, Backend oder serverlos?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#connecting-the-client">Verbindung des Clients</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#indexing-documents">Dokumente indizieren</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#elasticsearch-client">Elasticsearch-Client</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#semantic-mappings">Semantische Zuordnungen</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#bulk-helper">Schüttguthelfer</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#searching-data">Datensuche</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#lexical-query-(/search/lexic?q=%3Cquery-term%3E)">Lexikalische Abfrage</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#semantic-query-(/search/semantic?q=%3Cquery-term%3E)">Semantische Anfrage</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#hybrid-query-(/search/hybrid?q=%3Cquery-term%3E)">Hybridabfrage</a></p></li></ul></li></ul><p><em>Den Quellcode mit den Beispielen finden Sie </em><a href="https://github.com/Delacrobix/JS-client-best-practices_article"><em><strong>hier</strong></em></a><em><strong>.</strong></em></p><h3>Was ist der Elasticsearch Node.js-Client?</h3><p>Der <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html">Elasticsearch Node.js-Client</a> ist eine JavaScript-Bibliothek, die die HTTP-REST-Aufrufe der Elasticsearch-API in JavaScript umsetzt. Dadurch wird die Handhabung einfacher und es stehen Hilfsfunktionen zur Verfügung, die Aufgaben wie das Indizieren von Dokumenten in Stapeln vereinfachen.</p><h2>Umfeld</h2><h3>Frontend, Backend oder serverlos?</h3><p>Um unsere Such-App mit dem JavaScript-Client zu erstellen, benötigen wir mindestens zwei Komponenten: einen Elasticsearch-Cluster und eine JavaScript-Laufzeitumgebung zum Ausführen des Clients.</p><p>Der JavaScript-Client unterstützt alle Elasticsearch-Lösungen (Cloud, On-Premise und Serverless), und es gibt keine wesentlichen Unterschiede zwischen ihnen, da der Client alle Varianten intern handhabt, sodass Sie sich keine Gedanken darüber machen müssen, welche Sie verwenden sollen.</p><p>Die JavaScript-Laufzeitumgebung muss jedoch vom <strong>Server</strong> und <strong>nicht direkt vom Browser ausgeführt werden.</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3ec469c83e3a71a/6a17e3d5445de91da44d00b6/92ce6cfd923c8008fa44f617a58193642d9d5879-661x410.png" alt="Elasticsearch in einer JavaScript-Umgebung." /><p>Dies liegt daran, dass beim Aufruf von Elasticsearch über den Browser sensible Informationen wie der Cluster-API-Schlüssel, der Host oder die Abfrage selbst abgerufen werden können. Elasticsearch empfiehlt, <strong>den Cluster niemals direkt dem Internet auszusetzen </strong>und stattdessen eine Zwischenschicht zu verwenden, die all diese Informationen abstrahiert, sodass der Benutzer nur die Parameter sehen kann. <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/es-security-principles.html#security-protect-cluster-traffic">Hier</a> können Sie mehr zu diesem Thema lesen.</p><p>Wir schlagen vor, folgendes Schema zu verwenden:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d7f215f2e70230a/6a17e3d6fbc5f83de6491a13/a08769f08ec73fe57bf2e961cfdfbb1cdd57919d-972x429.png" alt="Den Elasticsearch Node.js-Client einrichten." /><p>In diesem Fall sendet der Client lediglich die Suchbegriffe und einen Authentifizierungsschlüssel an Ihren Server, während Ihr Server die vollständige Kontrolle über die Abfrage und die Kommunikation mit Elasticsearch behält.</p><h3>Verbindung des Clients</h3><p>Erstellen Sie zunächst einen API-Schlüssel gemäß <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">diesen Schritten</a>.</p><p>Im Anschluss an das vorherige Beispiel erstellen wir einen einfachen Express-Server und stellen über einen Client von einem Node.JS-Server aus eine Verbindung zu diesem her.</p><p>Wir werden das Projekt mit NPM initialisieren und den Elasticsearch-Client sowie <a href="https://expressjs.com/">Express installieren.</a> Letzteres ist eine Bibliothek zum Starten von Servern in Node.js. Mit Express können wir über HTTP mit unserem Backend interagieren.</p><p>Lasst uns das Projekt initialisieren:</p><p><code>npm init -y</code></p><p>Abhängigkeiten installieren:</p><p><code>npm install @elastic/elasticsearch express split2 dotenv</code></p><p>Ich erkläre es Ihnen genauer:</p><ul><li><p><a href="https://www.npmjs.com/package/@elastic/elasticsearch"><em><strong>@elastic/elasticsearch</strong></em></a>: Es handelt sich um den offiziellen Node.js-Client.</p></li><li><p><a href="https://www.npmjs.com/package/express"><em><strong>Express</strong></em></a>: Damit können wir einen schlanken Node.js-Server starten, um Elasticsearch bereitzustellen.</p></li><li><p><a href="https://www.npmjs.com/package/split2"><em><strong>split2</strong></em></a>: Teilt Textzeilen in einen Datenstrom auf. Nützlich, um unsere ndjson-Dateien zeilenweise zu verarbeiten.</p></li><li><p><a href="https://www.npmjs.com/package/dotenv"><em><strong>dotenv</strong></em></a>: Ermöglicht die Verwaltung von Umgebungsvariablen mithilfe einer .env-Datei. Datei</p></li></ul><p>Erstelle eine .env-Datei Fügen Sie in der Datei im Stammverzeichnis des Projekts die folgenden Zeilen hinzu:</p>ELASTICSEARCH_ENDPOINT="Your Elasticsearch endpoint"
ELASTICSEARCH_API_KEY="Your Elasticssearch API"<p>Auf diese Weise können wir diese Variablen mithilfe des Pakets <code>dotenv</code> importieren.</p><p>Erstelle eine <code>server.js</code> -Datei:</p>const express = require("express");
const bodyParser = require("body-parser");
const { Client } = require("@elastic/elasticsearch");
 
require("dotenv").config(); //environment variables setup

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


const app = express();

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


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

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

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

    res.status(500).json({
      success: false,
      clusterInfo: null,
      error: error.message,
    });
  }
});<p>Dieser Code richtet einen einfachen Express.js-Server ein, der auf Port 3000 lauscht und sich über einen API-Schlüssel zur Authentifizierung mit einem Elasticsearch-Cluster verbindet. Es beinhaltet einen /ping-Endpunkt, der bei einem GET-Aufruf mithilfe der <code>.info()</code> -Methode des Elasticsearch-Clients grundlegende Informationen vom Elasticsearch-Cluster abfragt. </p><p>Bei erfolgreicher Abfrage werden die Clusterinformationen im JSON-Format zurückgegeben; andernfalls wird eine Fehlermeldung zurückgegeben. Der Server verwendet außerdem die Middleware body-parser, um JSON-Anfragetexte zu verarbeiten.</p><p>Führen Sie die Datei aus, um den Server zu starten:</p><p><code>node server.js</code></p><p>Die Antwort sollte folgendermaßen aussehen:</p>Server running on port 3000<p>Und nun konsultieren wir den Endpunkt <code>/ping</code> , um den Status unseres Elasticsearch-Clusters zu überprüfen.</p>curl http://localhost:3000/ping
{
    "success": true,
    "clusterInfo": {
        "name": "instance-0000000000",
        "cluster_name": "61b7e19eec204d59855f5e019acd2689",
        "cluster_uuid": "BIfvfLM0RJWRK_bDCY5ldg",
        "version": {
            "number": "9.0.0",
            "build_flavor": "default",
            "build_type": "docker",
            "build_hash": "112859b85d50de2a7e63f73c8fc70b99eea24291",
            "build_date": "2025-04-08T15:13:46.049795831Z",
            "build_snapshot": false,
            "lucene_version": "10.1.0",
            "minimum_wire_compatibility_version": "8.18.0",
            "minimum_index_compatibility_version": "8.0.0"
        },
        "tagline": "You Know, for Search"
    }
}<h2>Dokumente indizieren</h2><p>Sobald die Verbindung hergestellt ist, können wir Dokumente mithilfe von Zuordnungen wie <a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">semantic_text</a> für die semantische Suche und text für Volltextabfragen indizieren. Mit diesen beiden Feldtypen können wir auch <a href="https://www.elastic.co/what-is/hybrid-search">eine hybride Suche</a> durchführen.</p><p>Wir erstellen eine neue <code>load.js</code> -Datei, um die Zuordnungen zu generieren und die Dokumente hochzuladen.</p><h3>Elasticsearch-Client</h3><p>Zuerst müssen wir den Client instanziieren und authentifizieren:</p>const { Client } = require("@elastic/elasticsearch");

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

const esClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { apiKey: ELASTICSEARCH_API_KEY },
});<h3>Semantische Zuordnungen</h3><p>Wir erstellen einen Index mit Daten über Tierkliniken. Wir speichern die Informationen über den Besitzer, das Haustier und die Details des Besuchs.</p><p>Die Daten, in denen wir eine Volltextsuche durchführen möchten, wie zum Beispiel Namen und Beschreibungen, werden als Text gespeichert. Die Daten aus Kategorien, wie zum Beispiel die Tierart oder -rasse, werden als Schlüsselwörter gespeichert.</p><p>Zusätzlich kopieren wir die Werte aller Felder in ein semantisches Textfeld, um auch mit diesen Informationen eine semantische Suche durchführen zu können.</p>const INDEX_NAME = "vet-visits";

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

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

await createMappings(INDEX_NAME, {
  properties: {
    owner_name: {
      type: "text",
      copy_to: "semantic_field",
    },
    pet_name: {
      type: "text",
      copy_to: "semantic_field",
    },
    species: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    breed: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    vaccination_history: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    visit_details: {
      type: "text",
      copy_to: "semantic_field",
    },
    semantic_field: {
      type: "semantic_text",
    },
  },
});<h3>Schüttguthelfer</h3><p>Ein weiterer Vorteil des Clients besteht darin, dass wir den <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-helpers.html#bulk-helper">Bulk-Helper</a> verwenden können, um die Indizierung in Batches durchzuführen. Der Bulk-Helper ermöglicht es uns, Dinge wie Parallelverarbeitung, Wiederholungsversuche und die Behandlung jedes Dokuments, das die Funktion durchläuft und erfolgreich oder fehlschlägt, einfach zu handhaben.</p><p>Ein attraktives Merkmal dieses Helfers ist, dass man mit Datenströmen arbeiten kann. Mit dieser Funktion können Sie eine Datei zeilenweise senden, anstatt die gesamte Datei im Speicher zu speichern und sie dann auf einmal an Elasticsearch zu senden.</p><p>Um die Daten in Elasticsearch hochzuladen, erstellen Sie eine Datei namens data.ndjson im Stammverzeichnis des Projekts und fügen Sie die folgenden Informationen hinzu (alternativ können Sie die Datei mit dem Datensatz <a href="https://github.com/Delacrobix/JS-client-best-practices_article/blob/main/data.ndjson">hier</a> herunterladen):</p>{"owner_name":"Alice Johnson","pet_name":"Buddy","species":"Dog","breed":"Golden Retriever","vaccination_history":["Rabies","Parvovirus","Distemper"],"visit_details":"Annual check-up and nail trimming. Healthy and active."}
{"owner_name":"Marco Rivera","pet_name":"Milo","species":"Cat","breed":"Siamese","vaccination_history":["Rabies","Feline Leukemia"],"visit_details":"Slight eye irritation, prescribed eye drops."}
{"owner_name":"Sandra Lee","pet_name":"Pickles","species":"Guinea Pig","breed":"Mixed","vaccination_history":[],"visit_details":"Loss of appetite, recommended dietary changes."}
{"owner_name":"Jake Thompson","pet_name":"Luna","species":"Dog","breed":"Labrador Mix","vaccination_history":["Rabies","Bordetella"],"visit_details":"Mild ear infection, cleaning and antibiotics given."}
{"owner_name":"Emily Chen","pet_name":"Ziggy","species":"Cat","breed":"Mixed","vaccination_history":["Rabies","Feline Calicivirus"],"visit_details":"Vaccination update and routine physical."}
{"owner_name":"Tomás Herrera","pet_name":"Rex","species":"Dog","breed":"German Shepherd","vaccination_history":["Rabies","Parvovirus","Leptospirosis"],"visit_details":"Follow-up for previous leg strain, improving well."}
{"owner_name":"Nina Park","pet_name":"Coco","species":"Ferret","breed":"Mixed","vaccination_history":["Rabies"],"visit_details":"Slight weight loss; advised new diet."}
{"owner_name":"Leo Martínez","pet_name":"Simba","species":"Cat","breed":"Maine Coon","vaccination_history":["Rabies","Feline Panleukopenia"],"visit_details":"Dental cleaning. Minor tartar buildup removed."}
{"owner_name":"Rachel Green","pet_name":"Rocky","species":"Dog","breed":"Bulldog Mix","vaccination_history":["Rabies","Parvovirus"],"visit_details":"Skin rash, antihistamines prescribed."}
{"owner_name":"Daniel Kim","pet_name":"Mochi","species":"Rabbit","breed":"Mixed","vaccination_history":[],"visit_details":"Nail trimming and general health check. No issues."}<p>Wir verwenden split2, um die Dateizeilen zu streamen, während der Bulk-Helper sie an Elasticsearch sendet.</p>const { createReadStream } = require("fs");
const split = require("split2");
 
const indexData = async (filePath, indexName) =&gt; {
  try {
    console.log(`Indexing data from ${filePath} into ${indexName}...`);

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

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

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

await indexData("./data.ndjson", INDEX_NAME);<p>Der obige Code liest eine .ndjson-Datei. Mit der Methode <code>helpers.bulk</code> werden alle JSON-Objekte zeilenweise und in einem Massenindex in einen angegebenen Elasticsearch-Index indiziert. Es streamt die Datei mit <code>createReadStream</code> und <code>split2</code>, richtet Indexierungsmetadaten für jedes Dokument ein und protokolliert alle Dokumente, deren Verarbeitung fehlschlägt. Nach Abschluss des Vorgangs wird die Anzahl der erfolgreich indizierten Elemente protokolliert.</p><p>Alternativ zur <code>indexData</code> -Funktion können Sie die Datei auch direkt über die Benutzeroberfläche von Kibana hochladen und dabei die <a href="https://www.elastic.co/docs/manage-data/ingest/upload-data-files">Funktion „Datendateien hochladen“ verwenden.</a></p><p>Wir führen die Datei aus, um die Dokumente in unseren Elasticsearch-Cluster hochzuladen.</p><p><code>node load.js</code></p>Creating mappings for index vet-visits...
Index created successfully: { acknowledged: true, shards_acknowledged: true, index: 'vet-visits' }
Indexing data from ./data.ndjson into vet-visits...
Bulk indexing completed. Total documents: 10, Failed: 0<h2>Datensuche in Elasticsearch</h2><p>Wir kehren zu unserer <code>server.js</code> -Datei zurück und erstellen verschiedene Endpunkte, um lexikalische, semantische oder hybride Suchen durchzuführen.</p><p>Kurz gesagt, schließen sich diese Sucharten nicht gegenseitig aus, sondern hängen von der Art der Frage ab, die Sie beantworten möchten.</p><p>Abfragetyp</p><p>Anwendungsfall</p><p>Beispielaufgabe</p><p>Lexikalische Abfrage</p><p>Die in der Frage vorkommenden Wörter oder Wortstämme tauchen wahrscheinlich in den Indexdokumenten auf. Tokenische Ähnlichkeit zwischen Frage und Dokumenten.</p><p>Ich suche ein blaues Sport-T-Shirt.</p><p>Semantische Anfrage</p><p>Die in der Frage enthaltenen Wörter werden in den Dokumenten wahrscheinlich nicht vorkommen. Konzeptionelle Ähnlichkeit zwischen Frage und Dokumenten.</p><p>Ich suche Kleidung für kaltes Wetter.</p><p>Hybrid Search</p><p>Die Frage enthält lexikalische und/oder semantische Komponenten. Token- und semantische Ähnlichkeit zwischen Frage und Dokumenten.</p><p>Ich suche ein Kleid in Größe S für eine Strandhochzeit.</p><p>Die <em><strong>lexikalischen </strong></em>Bestandteile der Frage sind wahrscheinlich Teil von Titeln und Beschreibungen oder Kategorienamen, während die <em><strong>semantischen </strong></em>Bestandteile Konzepte sind, die mit diesen Bereichen in Zusammenhang stehen. <em><strong>Blau</strong></em> wird wahrscheinlich ein Kategoriename oder Teil einer Beschreibung sein, <em><strong>Strandhochzeit</strong></em> hingegen eher nicht, kann aber semantisch mit Leinenkleidung in Verbindung gebracht werden.</p><h3>Lexikalische Abfrage (/search/lexic?q=)&lt;query_term&gt;</h3><p>Die lexikalische Suche, auch Volltextsuche genannt, bedeutet die Suche auf der Grundlage der Ähnlichkeit von Wörtern; das heißt, nach einer Analyse werden die Dokumente zurückgegeben, die die gesuchten Wörter enthalten.</p><p><a href="https://www.elastic.co/demo-gallery/lexical-search">Hier</a> finden Sie unser praktisches Tutorial zur lexikalischen Suche.</p>app.get("/search/lexic", async (req, res) =&gt; {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

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

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

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});<p>Wir testen mit: <em><strong>Krallenschneiden</strong></em></p>curl http://localhost:3000/search/lexic?q=nail%20trimming<p>Antwort:</p>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 2.7075968,
            "_source": {
                "pet_name": "Mochi",
                "owner_name": "Daniel Kim",
                "species": "Rabbit",
                "visit_details": "Nail trimming and general health check. No issues.",
                "breed": "Mixed",
                "vaccination_history": []
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 2.560356,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        }
    ]
}<h3>Semantische Abfrage (/search/semantic?q=)&lt;query_term&gt;</h3><p>Die semantische Suche findet, anders als die lexikalische Suche, Ergebnisse, die der Bedeutung der Suchbegriffe ähneln, durch Vektorsuche.</p><p><a href="https://www.elastic.co/demo-gallery/semantic-search">Hier</a> finden Sie unser praktisches Tutorial zur semantischen Suche.</p>app.get("/search/semantic", async (req, res) =&gt; {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

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

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

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});<p>Wir testen mit: <em><strong>Wer hat sich eine Pediküre machen lassen?</strong></em></p>curl http://localhost:3000/search/semantic?q=Who%20got%20a%20pedicure?<p>Antwort:</p>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 4.861466,
            "_source": {
                "owner_name": "Daniel Kim",
                "pet_name": "Mochi",
                "species": "Rabbit",
                "breed": "Mixed",
                "vaccination_history": [],
                "visit_details": "Nail trimming and general health check. No issues."
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 4.7152824,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9RY6RJYBLe2GoFQ6-9n9",
            "_score": 1.6717153,
            "_source": {
                "pet_name": "Rex",
                "owner_name": "Tomás Herrera",
                "species": "Dog",
                "visit_details": "Follow-up for previous leg strain, improving well.",
                "breed": "German Shepherd",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Leptospirosis"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9xY6RJYBLe2GoFQ6-9n9",
            "_score": 1.5600781,
            "_source": {
                "pet_name": "Simba",
                "owner_name": "Leo Martínez",
                "species": "Cat",
                "visit_details": "Dental cleaning. Minor tartar buildup removed.",
                "breed": "Maine Coon",
                "vaccination_history": [
                    "Rabies",
                    "Feline Panleukopenia"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "-BY6RJYBLe2GoFQ6-9n9",
            "_score": 1.2696637,
            "_source": {
                "pet_name": "Rocky",
                "owner_name": "Rachel Green",
                "species": "Dog",
                "visit_details": "Skin rash, antihistamines prescribed.",
                "breed": "Bulldog Mix",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus"
                ]
            }
        }
    ]
}<h3>Hybrid-Suchanfrage (/search/hybrid?q=)&lt;query_term&gt;</h3><p>Die Hybridsuche ermöglicht es uns, semantische und lexikalische Suche zu kombinieren und so das Beste aus beiden Welten zu erhalten: Sie erhalten die Präzision der Token-Suche zusammen mit der Bedeutungsnähe der semantischen Suche.</p>app.get("/search/hybrid", async (req, res) =&gt; {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

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

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

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});<p>Wir testen mit der Frage: „ <em><strong>Wer hat sich eine Pediküre oder eine Zahnbehandlung gegönnt?“</strong></em></p>curl http://localhost:3000/search/hybrid?q=who%20got%20a%20pedicure%20or%20dental%20treatment<p>Abwehr:</p>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "9xY6RJYBLe2GoFQ6-9n9",
            "_score": 0.032522473,
            "_source": {
                "pet_name": "Simba",
                "owner_name": "Leo Martínez",
                "species": "Cat",
                "visit_details": "Dental cleaning. Minor tartar buildup removed.",
                "breed": "Maine Coon",
                "vaccination_history": [
                    "Rabies",
                    "Feline Panleukopenia"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 0.016393442,
            "_source": {
                "pet_name": "Mochi",
                "owner_name": "Daniel Kim",
                "species": "Rabbit",
                "visit_details": "Nail trimming and general health check. No issues.",
                "breed": "Mixed",
                "vaccination_history": []
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015873017,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9RY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015625,
            "_source": {
                "pet_name": "Rex",
                "owner_name": "Tomás Herrera",
                "species": "Dog",
                "visit_details": "Follow-up for previous leg strain, improving well.",
                "breed": "German Shepherd",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Leptospirosis"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8xY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015384615,
            "_source": {
                "pet_name": "Luna",
                "owner_name": "Jake Thompson",
                "species": "Dog",
                "visit_details": "Mild ear infection, cleaning and antibiotics given.",
                "breed": "Labrador Mix",
                "vaccination_history": [
                    "Rabies",
                    "Bordetella"
                ]
            }
        }
    ]
}<h2>Fazit</h2><p>Im ersten Teil unserer Serie haben wir erklärt, wie man die Umgebung einrichtet und einen Server mit verschiedenen Suchendpunkten erstellt, um die Elasticsearch-Dokumente gemäß den Best Practices für Client/Server abzufragen. Schauen Sie sich <a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i">den zweiten Teil</a> unserer Serie an, in dem Sie Best Practices für die Produktion kennenlernen und erfahren, wie Sie den Elasticsearch Node.js-Client in serverlosen Umgebungen ausführen.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i</guid>
    <category><![CDATA[Javascript]]></category>
    <category><![CDATA[Grundlagen]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16d00c8a548b32e8/6a17e3d8fbc5f8c740491a19/72200540ed258779d87e53a72ea189f8a138540c-1600x901.png" length="0" type="image/png"/>
    <pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>