Blog

Führen Sie eine Vektorsuche in Elasticsearch mit dem Elasticsearch Go-Client durch.

Lernen Sie anhand eines praktischen Beispiels, wie Sie mit dem Elasticsearch Go-Client eine Vektorsuche in Elasticsearch durchführen.

Softwareentwicklung in jeder Programmiersprache, einschließlich Go, bedeutet die Verpflichtung zu lebenslangem Lernen. Während ihrer Universitäts- und Berufslaufbahn hat Carly mit vielen Programmiersprachen und Technologien experimentiert, darunter auch mit den neuesten und besten Implementierungen der Vektorsuche. Aber das reichte nicht! Seit Kurzem spielt auch Carly Go.

Genau wie Tiere, Programmiersprachen und Ihr freundlicher Autor hat sich auch die Suche im Laufe der Zeit weiterentwickelt und verschiedene Praktiken hervorgebracht, bei denen es schwierig sein kann, die richtige für Ihren individuellen Anwendungsfall zu finden. In diesem Blog geben wir einen Überblick über die Vektorsuche und zeigen Beispiele für die einzelnen Ansätze mit Elasticsearch und dem Elasticsearch Go-Client. Anhand dieser Beispiele wird Ihnen gezeigt, wie Sie mit Hilfe der Vektorsuche in Elasticsearch und Go Taschenratten finden und deren Nahrung bestimmen können.

Voraussetzungen

Um diesem Beispiel folgen zu können, stellen Sie sicher, dass die folgenden Voraussetzungen erfüllt sind:

  1. Installation von Go Version 1.21 oder höher

  2. Erstellung eines eigenen Go-Repositorys mit dem

  3. Erstellung eines eigenen Elasticsearch-Clusters, gefüllt mit einer Reihe von Seiten über Nagetiere, darunter auch für unseren freundlichen Gopher, aus Wikipedia:

Wikipedia-Seite über Gopher

Verbindung zu Elasticsearch herstellen

In unseren Beispielen werden wir die vom Go-Client angebotene Typed API verwenden. Um eine sichere Verbindung für jede Anfrage herzustellen, muss der Client auf folgende Weise konfiguriert werden:

  1. Cloud-ID und API-Schlüssel bei Verwendung von Elastic Cloud.

  2. Cluster-URL, Benutzername, Passwort und Zertifikat.

Die Verbindung zu unserem Cluster in der Elastic Cloud würde folgendermaßen aussehen:

func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
	var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
	var apiKey = os.Getenv("ELASTIC_API_KEY")

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

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

	return es, nil
}

Die client -Verbindung kann dann für die Vektorsuche verwendet werden, wie in den nachfolgenden Abschnitten gezeigt wird.

Vektorsuche

Die Vektorsuche versucht, dieses Problem zu lösen, indem sie das Suchproblem in einen mathematischen Vergleich mithilfe von Vektoren umwandelt. Der Dokumenteneinbettungsprozess beinhaltet einen zusätzlichen Schritt, bei dem das Dokument mithilfe eines Modells in eine dichte Vektordarstellung oder einfach in einen Zahlenstrom umgewandelt wird. Der Vorteil dieses Ansatzes besteht darin, dass man auch nicht-textuelle Dokumente wie Bilder und Audiodateien durchsuchen kann, indem man sie zusammen mit einer Suchanfrage in einen Vektor übersetzt.

Vektorsuchdiagramm

Vereinfacht ausgedrückt ist die Vektorsuche eine Reihe von Vektordistanzberechnungen. In der folgenden Abbildung wird die Vektordarstellung unserer Anfrage Go Gophermit den Dokumenten im Vektorraum verglichen, und die nächstliegenden Ergebnisse (gekennzeichnet durch die Konstante k) werden zurückgegeben:

Gopher-Vektorraum-Beispiel

Je nachdem, welcher Ansatz zur Generierung der Einbettungen für Ihre Dokumente verwendet wird, gibt es zwei verschiedene Wege, um herauszufinden, was Taschenratten fressen.

Ansatz 1: Bringen Sie Ihr eigenes Modell mit

Mit einer Platinum-Lizenz ist es möglich, die Einbettungen innerhalb von Elasticsearch zu generieren, indem das Modell hochgeladen und die Inferenz-API verwendet wird. Die Einrichtung des Modells umfasst sechs Schritte:

  1. Wählen Sie ein PyTorch-Modell aus einem Modell-Repository zum Hochladen aus. Für dieses Beispiel verwenden wir den sentence-transformers/msmarco-MiniLM-L-12-v3 von Hugging Face, um die Einbettungen zu generieren.

  2. Laden Sie das Modell in Elastic mit dem Eland Machine Learning Client für Python unter Verwendung der Anmeldeinformationen für unseren Elasticsearch-Cluster und dem Aufgabentyp text_embeddings. Falls Eland nicht installiert ist, können Sie den Import-Schritt mit Docker ausführen, wie unten gezeigt:

docker run -it --rm --network host \
    docker.elastic.co/eland/eland \
    eland_import_hub_model \
      --cloud-id $ELASTIC_CLOUD_ID \
      --es-api-key $ELASTIC_API_KEY \
      --hub-model-id sentence-transformers/msmarco-MiniLM-L-12-v3 \
      --task-type text_embedding
  1. Nach dem Hochladen sollte das Modell sentence-transformers__msmarco-minilm-l-12-v3 schnell mit einem Beispieldokument getestet werden, um sicherzustellen, dass die Einbettungen wie erwartet generiert werden:

Beispiel für ein trainiertes Modell im Elastizitätstest
  1. Erstellen Sie eine Ingest-Pipeline, die einen Inferenzprozessor enthält. Dadurch kann die Vektordarstellung mithilfe des hochgeladenen Modells generiert werden:

PUT _ingest/pipeline/search-rodents-vector-embedding-pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "sentence-transformers__msmarco-minilm-l-12-v3",
        "target_field": "text_embedding",
        "field_map": {
          "body_content": "text_field"
        }
      }
    }
  ]
}
  1. Erstellen Sie einen neuen Index, der das Feld text_embedding.predicted_value vom Typ dense_vector enthält, um die für jedes Dokument generierten Vektoreinbettungen zu speichern:

PUT vector-search-rodents
{
  "mappings": {
    "properties": {
      "text_embedding.predicted_value": {
        "type": "dense_vector",
        "dims": 384,
        "index": true,
        "similarity": "cosine"
      },
      "text": {
        "type": "text"
      }
    }
  }
}
  1. Die Dokumente werden mithilfe der neu erstellten Ingest-Pipeline neu indiziert, um die Text-Embeddings als zusätzliches Feld text_embedding.predicted_value in jedem Dokument zu generieren:

POST _reindex
{
  "source": {
    "index": "search-rodents"
  },
  "dest": {
    "index": "vector-search-rodents",
    "pipeline": "search-rodents-vector-embedding-pipeline"
  }
}

Nun können wir die Option Knn in derselben Such-API mit dem neuen Index vector-search-rodents verwenden, wie im folgenden Beispiel gezeigt:

func VectorSearch(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
  var k = 10
	var numCandidates = 10

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

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

	return getRodents(res.Hits.Hits)
}

Die Konvertierung des JSON-Ergebnisobjekts mittels Unmarshalling erfolgt auf genau die gleiche Weise wie im Beispiel der Stichwortsuche. Mithilfe der Konstanten K und NumCandidates können wir die Anzahl der zurückzugebenden Nachbardokumente und die Anzahl der pro Shard zu berücksichtigenden Kandidaten konfigurieren. Beachten Sie, dass eine Erhöhung der Anzahl der Kandidaten zwar die Genauigkeit der Ergebnisse erhöht, aber zu einer längeren Abfragedauer führt, da mehr Vergleiche durchgeführt werden.

Wird der Code mit der Abfrage What do Gophers eat? ausgeführt, sehen die zurückgegebenen Ergebnisse ähnlich wie unten dargestellt aus, was verdeutlicht, dass der Gopher-Artikel im Gegensatz zur vorherigen Stichwortsuche die angeforderten Informationen enthält:

[
  {ID:64f74ecd4acb3df024d91112 Title:Gopher - Wikipedia Url:https://en.wikipedia.org/wiki/Gopher} 
  {ID:64f74ed34acb3d71aed91fcd Title:Squirrel - Wikipedia Url:https://en.wikipedia.org/wiki/Squirrel} 
  //Other results omitted
]

Ansatz 2: Hugging Face Inference API

Eine weitere Möglichkeit besteht darin, diese Einbettungen außerhalb von Elasticsearch zu generieren und sie als Teil Ihres Dokuments einzubinden. Da diese Option keinen Elasticsearch Machine Learning-Knoten verwendet, kann sie im kostenlosen Tarif durchgeführt werden.

Hugging Face stellt eine kostenlose, ratenbegrenzte Inferenz-API zur Verfügung, mit der Sie mithilfe eines Kontos und eines API-Tokens dieselben Einbettungen manuell für Experimente und Prototyping generieren können, um Ihnen den Einstieg zu erleichtern. Für den Einsatz in der Produktion wird es nicht empfohlen. Das lokale Aufrufen eigener Modelle zur Generierung von Einbettungen oder die Nutzung der kostenpflichtigen API kann ebenfalls mit einem ähnlichen Ansatz erfolgen.

In der folgenden Funktion GetTextEmbeddingForQuery verwenden wir die Inferenz-API anhand unserer Abfragezeichenfolge, um den Vektor zu generieren, der von einer POST Anfrage an den Endpunkt zurückgegeben wird:

// HuggingFace text embedding helper
func GetTextEmbeddingForQuery(term string) []float32 {
    // HTTP endpoint
    model := "sentence-transformers/msmarco-minilm-l-12-v3"
    posturl := fmt.Sprintf("https://api-inference.huggingface.co/pipeline/feature-extraction/%s", model)

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

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

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

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

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

    defer res.Body.Close()

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

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

    return post
}

Der resultierende Vektor vom Typ []float32 wird dann als QueryVector übergeben, anstatt die Option QueryVectorBuilder zu verwenden, um das zuvor in Elastic hochgeladene Modell zu nutzen.

func VectorSearchWithGeneratedQueryVector(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	vector, err := GetTextEmbeddingForQuery(term)
	if err != nil {
		return nil, err
	}

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

  var k = 10
	var numCandidates = 10

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

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}

Beachten Sie, dass die Optionen K und NumCandidates unabhängig von den beiden Optionen gleich bleiben und dass die gleichen Ergebnisse generiert werden, da wir dasselbe Modell zur Generierung der Einbettungen verwenden.

Fazit

Hier haben wir besprochen, wie man eine Vektorsuche in Elasticsearch mit dem Elasticsearch Go-Client durchführt. Im GitHub-Repository findet ihr den gesamten Code dieser Serie. Im Anschluss daran lesen Sie Teil 3, um einen Überblick über die Kombination der Vektorsuche mit den in Teil eins behandelten Schlüsselwortsuchfunktionen in Go zu erhalten.

Bis dahin, viel Erfolg bei der Gopherjagd!

Ressourcen

  1. Elasticsearch-Leitfaden

  2. Elasticsearch Go-Client

  3. Was ist Vektorsuche? | Elastisch

Zugehörige Inhalte

LINQ to Elasticsearch ES|QL: C# schreiben, Elasticsearch abfragen

Florian Bernd

Erweiterung der Chatbot-Funktionen mit NLP und Vektorsuche in Elasticsearch

Priscilla Parodi

Elasticsearch vs. OpenSearch: Vergleich der Leistung bei der Vektorsuche

Ugo Sangiorgi

KI-Plagiat: Plagiatserkennung mit Elasticsearch

Priscilla Parodi

Fortgeschrittene RAG-Techniken Teil 2: Abfragen und Testen

Han Xiang Choong

Sind Sie bereit, hochmoderne Sucherlebnisse zu schaffen?

Eine ausreichend fortgeschrittene Suche kann nicht durch die Bemühungen einer einzelnen Person erreicht werden. Elasticsearch wird von Datenwissenschaftlern, ML-Ops-Experten, Ingenieuren und vielen anderen unterstützt, die genauso leidenschaftlich an der Suche interessiert sind wie Sie. Lasst uns in Kontakt treten und zusammenarbeiten, um das magische Sucherlebnis zu schaffen, das Ihnen die gewünschten Ergebnisse liefert.

Probieren Sie es selbst aus