Wyszukiwanie z użyciem wektorów dystrybucyjnych wektorowych

Na stronie znajdziesz informacje o tym, jak za pomocą Cloud Firestore przeprowadzać wyszukiwania wektorowe K-najbliższych sąsiadów (KNN) przy użyciu tych technik:

  • Przechowywanie wartości wektorów
  • Tworzenie indeksów wektorowych KNN i zarządzanie nimi
  • Wykonywanie zapytania o K najbliższych sąsiadów (KNN) z użyciem jednej z obsługiwanych miar odległości wektorowej.

Przechowywanie wektorów dystrybucyjnych

Możesz tworzyć wartości wektorowe, takie jak wektory dystrybucyjne tekstu, na podstawie danych Cloud Firestore i przechowywać je w dokumentach Cloud Firestore.

Operacja zapisu z wektorowym osadzaniem

Ten przykład pokazuje, jak zapisać wektor dystrybucyjny w Cloud Firestoredokumencie:

Python
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

firestore_client = firestore.Client()
collection = firestore_client.collection("coffee-beans")
doc = {
    "name": "Kahawa coffee beans",
    "description": "Information about the Kahawa coffee beans.",
    "embedding_field": Vector([0.18332680, 0.24160706, 0.3416704]),
}

collection.add(doc)
Node.js
import {
  Firestore,
  FieldValue,
} from "@google-cloud/firestore";

const db = new Firestore();
const coll = db.collection('coffee-beans');
await coll.add({
  name: "Kahawa coffee beans",
  description: "Information about the Kahawa coffee beans.",
  embedding_field: FieldValue.vector([1.0 , 2.0, 3.0])
});
Go
import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/firestore"
)

type CoffeeBean struct {
	Name           string             `firestore:"name,omitempty"`
	Description    string             `firestore:"description,omitempty"`
	EmbeddingField firestore.Vector32 `firestore:"embedding_field,omitempty"`
	Color          string             `firestore:"color,omitempty"`
}

func storeVectors(w io.Writer, projectID string) error {
	ctx := context.Background()

	// Create client
	client, err := firestore.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("firestore.NewClient: %w", err)
	}
	defer client.Close()

	// Vector can be represented by Vector32 or Vector64
	doc := CoffeeBean{
		Name:           "Kahawa coffee beans",
		Description:    "Information about the Kahawa coffee beans.",
		EmbeddingField: []float32{1.0, 2.0, 3.0},
		Color:          "red",
	}
	ref := client.Collection("coffee-beans").NewDoc()
	if _, err = ref.Set(ctx, doc); err != nil {
		fmt.Fprintf(w, "failed to upsert: %v", err)
		return err
	}

	return nil
}
Java
import com.google.cloud.firestore.CollectionReference;
import com.google.cloud.firestore.DocumentReference;
import com.google.cloud.firestore.FieldValue;
import com.google.cloud.firestore.VectorQuery;

CollectionReference coll = firestore.collection("coffee-beans");

Map<String, Object> docData = new HashMap<>();
docData.put("name", "Kahawa coffee beans");
docData.put("description", "Information about the Kahawa coffee beans.");
docData.put("embedding_field", FieldValue.vector(new double[] {1.0, 2.0, 3.0}));

ApiFuture<DocumentReference> future = coll.add(docData);
DocumentReference documentReference = future.get();

Obliczanie wektorów dystrybucyjnych za pomocą funkcji w Cloud Functions

Aby obliczać i przechowywać osadzenia wektorowe za każdym razem, gdy dokument jest aktualizowany lub tworzony, możesz skonfigurować Cloud Function:

Python
@functions_framework.cloud_event
def store_embedding(cloud_event) -> None:
  """Triggers by a change to a Firestore document.
  """
  firestore_payload = firestore.DocumentEventData()
  payload = firestore_payload._pb.ParseFromString(cloud_event.data)

  collection_id, doc_id = from_payload(payload)
  # Call a function to calculate the embedding
  embedding = calculate_embedding(payload)
  # Update the document
  doc = firestore_client.collection(collection_id).document(doc_id)
  doc.set({"embedding_field": embedding}, merge=True)
Node.js
/**
 * A vector embedding will be computed from the
 * value of the `content` field. The vector value
 * will be stored in the `embedding` field. The
 * field names `content` and `embedding` are arbitrary
 * field names chosen for this example.
 */
async function storeEmbedding(event: FirestoreEvent<any>): Promise<void> {
  // Get the previous value of the document's `content` field.
  const previousDocumentSnapshot = event.data.before as QueryDocumentSnapshot;
  const previousContent = previousDocumentSnapshot.get("content");

  // Get the current value of the document's `content` field.
  const currentDocumentSnapshot = event.data.after as QueryDocumentSnapshot;
  const currentContent = currentDocumentSnapshot.get("content");

  // Don't update the embedding if the content field did not change
  if (previousContent === currentContent) {
    return;
  }

  // Call a function to calculate the embedding for the value
  // of the `content` field.
  const embeddingVector = calculateEmbedding(currentContent);

  // Update the `embedding` field on the document.
  await currentDocumentSnapshot.ref.update({
    embedding: embeddingVector,
  });
}
Go
  // Not yet supported in the Go client library
Java
  // Not yet supported in the Java client library

Tworzenie indeksów wektorowych i zarządzanie nimi

Zanim przeprowadzisz wyszukiwanie najbliższych sąsiadów za pomocą osadzania wektorowego, musisz utworzyć odpowiedni indeks. Poniższe przykłady pokazują, jak tworzyć indeksy wektorowe i nimi zarządzać za pomocą Google Cloud CLI. Indeksami wektorowymi można też zarządzać za pomocą wiersza poleceń Firebase i Terraform.

Tworzenie indeksu wektorowego

Zanim utworzysz indeks wektorowy, uaktualnij bibliotekę Google Cloud CLI do najnowszej wersji:

gcloud components update

Aby utworzyć indeks wektorowy, użyj gcloud firestore indexes composite create:

gcloud
gcloud firestore indexes composite create \
--collection-group=collection-group \
--query-scope=COLLECTION \
--field-config field-path=vector-field,vector-config='vector-configuration' \
--database=database-id

gdzie:

  • collection-group to identyfikator grupy kolekcji.
  • vector-field to nazwa pola zawierającego osadzenie wektorowe.
  • database-id to identyfikator bazy danych.
  • vector-configuration zawiera wektor dimension i typ indeksu. Wartość dimension to liczba całkowita do 2048. Typ indeksu musi być flat. Skonfiguruj indeks w ten sposób: {"dimension":"DIMENSION", "flat": "{}"}.

Poniższy przykład tworzy indeks złożony, który zawiera indeks wektorowy dla pola vector-field i indeks rosnący dla pola color. Możesz użyć tego typu indeksu do wstępnego filtrowania danych przed wyszukiwaniem najbliższych sąsiadów.

gcloud
gcloud firestore indexes composite create \
--collection-group=collection-group \
--query-scope=COLLECTION \
--field-config=order=ASCENDING,field-path="color" \
--field-config field-path=vector-field,vector-config='{"dimension":"1024", "flat": "{}"}' \
--database=database-id

Wyświetlanie listy wszystkich indeksów wektorowych

gcloud
gcloud firestore indexes composite list --database=database-id

Zastąp database-id identyfikatorem bazy danych.

Usuwanie indeksu wektorowego

gcloud
gcloud firestore indexes composite delete index-id --database=database-id

gdzie:

  • index-id to identyfikator indeksu do usunięcia. Użyj polecenia indexes composite list, aby pobrać identyfikator indeksu.
  • database-id to identyfikator bazy danych.

Opis indeksu wektorowego

gcloud
gcloud firestore indexes composite describe index-id --database=database-id

gdzie:

  • index-id to identyfikator indeksu, który chcesz opisać. Użyj polecenia lub indexes composite list, aby pobrać identyfikator indeksu.
  • database-id to identyfikator bazy danych.

Tworzenie zapytania o najbliższych sąsiadów

Możesz przeprowadzić wyszukiwanie podobieństwa, aby znaleźć najbliższych sąsiadów wektora dystrybucyjnego. Wyszukiwania podobieństw wymagają indeksów wektorowych. Jeśli indeks nie istnieje, Cloud Firestore sugeruje utworzenie indeksu za pomocą gcloud CLI.

W tym przykładzie wyszukujemy 10 najbliższych sąsiadów wektora zapytania.

Python
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

collection = db.collection("coffee-beans")

# Requires a single-field vector index
vector_query = collection.find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([0.3416704, 0.18332680, 0.24160706]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=5,
)
Node.js
import {
  Firestore,
  FieldValue,
  VectorQuery,
  VectorQuerySnapshot,
} from "@google-cloud/firestore";

// Requires a single-field vector index
const vectorQuery: VectorQuery = coll.findNearest({
  vectorField: 'embedding_field',
  queryVector: [3.0, 1.0, 2.0],
  limit: 10,
  distanceMeasure: 'EUCLIDEAN'
});

const vectorQuerySnapshot: VectorQuerySnapshot = await vectorQuery.get();
Go
import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/firestore"
)

func vectorSearchBasic(w io.Writer, projectID string) error {
	ctx := context.Background()

	// Create client
	client, err := firestore.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("firestore.NewClient: %w", err)
	}
	defer client.Close()

	collection := client.Collection("coffee-beans")

	// Requires a vector index
	// https://firebase.google.com/docs/firestore/vector-search#create_and_manage_vector_indexes
	vectorQuery := collection.FindNearest("embedding_field",
		[]float32{3.0, 1.0, 2.0},
		5,
		// More info: https://firebase.google.com/docs/firestore/vector-search#vector_distances
		firestore.DistanceMeasureEuclidean,
		nil)

	docs, err := vectorQuery.Documents(ctx).GetAll()
	if err != nil {
		fmt.Fprintf(w, "failed to get vector query results: %v", err)
		return err
	}

	for _, doc := range docs {
		fmt.Fprintln(w, doc.Data()["name"])
	}
	return nil
}
Java
import com.google.cloud.firestore.VectorQuery;
import com.google.cloud.firestore.VectorQuerySnapshot;

VectorQuery vectorQuery = coll.findNearest(
        "embedding_field",
        new double[] {3.0, 1.0, 2.0},
        /* limit */ 10,
        VectorQuery.DistanceMeasure.EUCLIDEAN);

ApiFuture<VectorQuerySnapshot> future = vectorQuery.get();
VectorQuerySnapshot vectorQuerySnapshot = future.get();

Odległości wektorowe

Zapytania o najbliższego sąsiada obsługują te opcje odległości wektorowej:

  • EUCLIDEAN: mierzy odległość EUCLIDEAN między wektorami. Więcej informacji znajdziesz w artykule Odległość euklidesowa.
  • COSINE: porównuje wektory na podstawie kąta między nimi, co pozwala mierzyć podobieństwo, które nie jest oparte na wielkości wektorów. Zamiast odległości COSINE zalecamy używanie DOT_PRODUCT ze znormalizowanymi wektorami jednostkowymi, co jest matematycznie równoważne i zapewnia lepszą wydajność. Więcej informacji znajdziesz w artykule Podobieństwo cosinusowe.
  • DOT_PRODUCT: podobny do COSINE, ale zależy od wielkości wektorów. Więcej informacji znajdziesz w artykule Iloczyn skalarny.

Wybierz pomiar odległości

W zależności od tego, czy wszystkie osadzenia wektorowe są znormalizowane, możesz określić, którego pomiaru odległości użyć do znalezienia pomiaru odległości. Znormalizowany wektor dystrybucyjny ma wielkość (długość) dokładnie 1,0.

Jeśli wiesz, jakiej miary odległości użyto do wytrenowania modelu, użyj jej do obliczenia odległości między wektorami dystrybucyjnymi.

Znormalizowane dane

Jeśli masz zbiór danych, w którym wszystkie osadzenia wektorowe są znormalizowane, wszystkie 3 miary odległości zapewniają te same wyniki wyszukiwania semantycznego. Chociaż każda miara odległości zwraca inną wartość, wartości te są sortowane w ten sam sposób. Gdy wektory są znormalizowane, DOT_PRODUCT jest zwykle najbardziej wydajny obliczeniowo, ale w większości przypadków różnica jest znikoma. Jeśli jednak Twoja aplikacja jest bardzo wrażliwa na wydajność, DOT_PRODUCT może pomóc w dostrajaniu wydajności.

Dane nieznormalizowane

Jeśli masz zbiór danych, w którym wektory dystrybucyjne nie są znormalizowane, używanie DOT_PRODUCT jako miary odległości jest matematycznie nieprawidłowe, ponieważ iloczyn skalarny nie mierzy odległości. W zależności od tego, jak zostały wygenerowane wektory i jaki typ wyszukiwania jest preferowany, miara odległości COSINE lub EUCLIDEAN daje wyniki wyszukiwania, które są subiektywnie lepsze niż wyniki uzyskane przy użyciu innych miar odległości. Aby określić, które z tych rozwiązań jest najlepsze w Twoim przypadku, może być konieczne przeprowadzenie eksperymentu z użyciem COSINE lub EUCLIDEAN.

Nie wiesz, czy dane są znormalizowane czy nie

Jeśli nie masz pewności, czy Twoje dane są znormalizowane, a chcesz użyć parametru DOT_PRODUCT, zalecamy użycie parametru COSINE. Funkcja COSINE działa podobnie do funkcji DOT_PRODUCT, ale ma wbudowaną normalizację. Odległość mierzona za pomocą COSINE mieści się w zakresie od 0 do 2. Wynik bliski 0 oznacza, że wektory są bardzo podobne.

Wstępne filtrowanie dokumentów

Aby wstępnie odfiltrować dokumenty przed znalezieniem najbliższych sąsiadów, możesz połączyć wyszukiwanie podobieństw z innymi operatorami zapytań. Obsługiwane są filtry złożone andor. Więcej informacji o obsługiwanych filtrach pól znajdziesz w sekcji Operatory zapytań.

Python
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

collection = db.collection("coffee-beans")

# Similarity search with pre-filter
# Requires a composite vector index
vector_query = collection.where("color", "==", "red").find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([0.3416704, 0.18332680, 0.24160706]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=5,
)
Node.js
// Similarity search with pre-filter
// Requires composite vector index
const preFilteredVectorQuery: VectorQuery = coll
    .where("color", "==", "red")
    .findNearest({
      vectorField: "embedding_field",
      queryVector: [3.0, 1.0, 2.0],
      limit: 5,
      distanceMeasure: "EUCLIDEAN",
    });

const vectorQueryResults = await preFilteredVectorQuery.get();
Go
import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/firestore"
)

func vectorSearchPrefilter(w io.Writer, projectID string) error {
	ctx := context.Background()

	// Create client
	client, err := firestore.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("firestore.NewClient: %w", err)
	}
	defer client.Close()

	collection := client.Collection("coffee-beans")

	// Similarity search with pre-filter
	// Requires a composite vector index
	vectorQuery := collection.Where("color", "==", "red").
		FindNearest("embedding_field",
			[]float32{3.0, 1.0, 2.0},
			5,
			// More info: https://firebase.google.com/docs/firestore/vector-search#vector_distances
			firestore.DistanceMeasureEuclidean,
			nil)

	docs, err := vectorQuery.Documents(ctx).GetAll()
	if err != nil {
		fmt.Fprintf(w, "failed to get vector query results: %v", err)
		return err
	}

	for _, doc := range docs {
		fmt.Fprintln(w, doc.Data()["name"])
	}
	return nil
}
Java
import com.google.cloud.firestore.VectorQuery;
import com.google.cloud.firestore.VectorQuerySnapshot;

VectorQuery preFilteredVectorQuery = coll
        .whereEqualTo("color", "red")
        .findNearest(
                "embedding_field",
                new double[] {3.0, 1.0, 2.0},
                /* limit */ 10,
                VectorQuery.DistanceMeasure.EUCLIDEAN);

ApiFuture<VectorQuerySnapshot> future = preFilteredVectorQuery.get();
VectorQuerySnapshot vectorQuerySnapshot = future.get();

Pobieranie obliczonej odległości wektora

Obliczoną odległość wektorową możesz pobrać, przypisując nazwę właściwości wyjściowej do zapytania FindNearest, jak pokazano w tym przykładzie:distance_result_field

Python
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

collection = db.collection("coffee-beans")

vector_query = collection.find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([0.3416704, 0.18332680, 0.24160706]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=10,
    distance_result_field="vector_distance",
)

docs = vector_query.stream()

for doc in docs:
    print(f"{doc.id}, Distance: {doc.get('vector_distance')}")
Node.js
const vectorQuery: VectorQuery = coll.findNearest(
    {
      vectorField: 'embedding_field',
      queryVector: [3.0, 1.0, 2.0],
      limit: 10,
      distanceMeasure: 'EUCLIDEAN',
      distanceResultField: 'vector_distance'
    });

const snapshot: VectorQuerySnapshot = await vectorQuery.get();

snapshot.forEach((doc) => {
  console.log(doc.id, ' Distance: ', doc.get('vector_distance'));
});
Go
import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/firestore"
)

func vectorSearchDistanceResultField(w io.Writer, projectID string) error {
	ctx := context.Background()

	client, err := firestore.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("firestore.NewClient: %w", err)
	}
	defer client.Close()

	collection := client.Collection("coffee-beans")

	// Requires a vector index
	// https://firebase.google.com/docs/firestore/vector-search#create_and_manage_vector_indexes
	vectorQuery := collection.FindNearest("embedding_field",
		[]float32{3.0, 1.0, 2.0},
		10,
		firestore.DistanceMeasureEuclidean,
		&firestore.FindNearestOptions{
			DistanceResultField: "vector_distance",
		})

	docs, err := vectorQuery.Documents(ctx).GetAll()
	if err != nil {
		fmt.Fprintf(w, "failed to get vector query results: %v", err)
		return err
	}

	for _, doc := range docs {
		fmt.Fprintf(w, "%v, Distance: %v\n", doc.Data()["name"], doc.Data()["vector_distance"])
	}
	return nil
}
Java
import com.google.cloud.firestore.VectorQuery;
import com.google.cloud.firestore.VectorQueryOptions;
import com.google.cloud.firestore.VectorQuerySnapshot;

VectorQuery vectorQuery = coll.findNearest(
        "embedding_field",
        new double[] {3.0, 1.0, 2.0},
        /* limit */ 10,
        VectorQuery.DistanceMeasure.EUCLIDEAN,
        VectorQueryOptions.newBuilder().setDistanceResultField("vector_distance").build());

ApiFuture<VectorQuerySnapshot> future = vectorQuery.get();
VectorQuerySnapshot vectorQuerySnapshot = future.get();

for (DocumentSnapshot document : vectorQuerySnapshot.getDocuments()) {
    System.out.println(document.getId() + " Distance: " + document.get("vector_distance"));
}

Jeśli chcesz użyć maski pola, aby zwrócić podzbiór pól dokumentu wraz z distanceResultField, musisz też uwzględnić w niej wartość distanceResultField, jak pokazano w tym przykładzie:

Python
vector_query = collection.select(["color", "vector_distance"]).find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([0.3416704, 0.18332680, 0.24160706]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=10,
    distance_result_field="vector_distance",
)
Node.js
const vectorQuery: VectorQuery = coll
    .select('name', 'description', 'vector_distance')
    .findNearest({
      vectorField: 'embedding_field',
      queryVector: [3.0, 1.0, 2.0],
      limit: 10,
      distanceMeasure: 'EUCLIDEAN',
      distanceResultField: 'vector_distance'
    });
Go
import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/firestore"
)

func vectorSearchDistanceResultFieldMasked(w io.Writer, projectID string) error {
	ctx := context.Background()

	client, err := firestore.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("firestore.NewClient: %w", err)
	}
	defer client.Close()

	collection := client.Collection("coffee-beans")

	// Requires a vector index
	// https://firebase.google.com/docs/firestore/vector-search#create_and_manage_vector_indexes
	vectorQuery := collection.Select("color", "vector_distance").
		FindNearest("embedding_field",
			[]float32{3.0, 1.0, 2.0},
			10,
			firestore.DistanceMeasureEuclidean,
			&firestore.FindNearestOptions{
				DistanceResultField: "vector_distance",
			})

	docs, err := vectorQuery.Documents(ctx).GetAll()
	if err != nil {
		fmt.Fprintf(w, "failed to get vector query results: %v", err)
		return err
	}

	for _, doc := range docs {
		fmt.Fprintf(w, "%v, Distance: %v\n", doc.Data()["color"], doc.Data()["vector_distance"])
	}
	return nil
}
Java
import com.google.cloud.firestore.VectorQuery;
import com.google.cloud.firestore.VectorQueryOptions;
import com.google.cloud.firestore.VectorQuerySnapshot;

VectorQuery vectorQuery = coll
        .select("name", "description", "vector_distance")
        .findNearest(
          "embedding_field",
          new double[] {3.0, 1.0, 2.0},
          /* limit */ 10,
          VectorQuery.DistanceMeasure.EUCLIDEAN,
          VectorQueryOptions.newBuilder()
            .setDistanceResultField("vector_distance")
            .build());

ApiFuture<VectorQuerySnapshot> future = vectorQuery.get();
VectorQuerySnapshot vectorQuerySnapshot = future.get();

for (DocumentSnapshot document : vectorQuerySnapshot.getDocuments()) {
    System.out.println(document.getId() + " Distance: " + document.get("vector_distance"));
}

Określanie progu odległości

Możesz określić próg podobieństwa, który będzie zwracać tylko dokumenty mieszczące się w tym progu. Zachowanie pola progu zależy od wybranej miary odległości:

  • Odległości EUCLIDEANCOSINE ograniczają próg do dokumentów, w których odległość jest mniejsza lub równa określonemu progowi. Te miary odległości zmniejszają się, gdy wektory stają się bardziej podobne.
  • DOT_PRODUCT distance ogranicza próg do dokumentów, w których odległość jest większa lub równa podanemu progowi. Odległości w formie iloczynu skalarnego rosną, gdy wektory stają się bardziej podobne.

Poniższy przykład pokazuje, jak określić próg odległości, aby zwrócić maksymalnie 10 najbliższych dokumentów, które znajdują się w odległości co najwyżej 4,5 jednostki, używając wskaźnika odległości EUCLIDEAN:

Python
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

collection = db.collection("coffee-beans")

vector_query = collection.find_nearest(
    vector_field="embedding_field",
    query_vector=Vector([0.3416704, 0.18332680, 0.24160706]),
    distance_measure=DistanceMeasure.EUCLIDEAN,
    limit=10,
    distance_threshold=4.5,
)

docs = vector_query.stream()

for doc in docs:
    print(f"{doc.id}")
Node.js
const vectorQuery: VectorQuery = coll.findNearest({
  vectorField: 'embedding_field',
  queryVector: [3.0, 1.0, 2.0],
  limit: 10,
  distanceMeasure: 'EUCLIDEAN',
  distanceThreshold: 4.5
});

const snapshot: VectorQuerySnapshot = await vectorQuery.get();

snapshot.forEach((doc) => {
  console.log(doc.id);
});
Go
import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/firestore"
)

func vectorSearchDistanceThreshold(w io.Writer, projectID string) error {
	ctx := context.Background()

	client, err := firestore.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("firestore.NewClient: %w", err)
	}
	defer client.Close()

	collection := client.Collection("coffee-beans")

	// Requires a vector index
	// https://firebase.google.com/docs/firestore/vector-search#create_and_manage_vector_indexes
	vectorQuery := collection.FindNearest("embedding_field",
		[]float32{3.0, 1.0, 2.0},
		10,
		firestore.DistanceMeasureEuclidean,
		&firestore.FindNearestOptions{
			DistanceThreshold: firestore.Ptr[float64](4.5),
		})

	docs, err := vectorQuery.Documents(ctx).GetAll()
	if err != nil {
		fmt.Fprintf(w, "failed to get vector query results: %v", err)
		return err
	}

	for _, doc := range docs {
		fmt.Fprintln(w, doc.Data()["name"])
	}
	return nil
}
Java
import com.google.cloud.firestore.VectorQuery;
import com.google.cloud.firestore.VectorQueryOptions;
import com.google.cloud.firestore.VectorQuerySnapshot;

VectorQuery vectorQuery = coll.findNearest(
        "embedding_field",
        new double[] {3.0, 1.0, 2.0},
        /* limit */ 10,
        VectorQuery.DistanceMeasure.EUCLIDEAN,
        VectorQueryOptions.newBuilder()
          .setDistanceThreshold(4.5)
          .build());

ApiFuture<VectorQuerySnapshot> future = vectorQuery.get();
VectorQuerySnapshot vectorQuerySnapshot = future.get();

for (DocumentSnapshot document : vectorQuerySnapshot.getDocuments()) {
    System.out.println(document.getId());
}

Ograniczenia

Podczas pracy z wektorami dystrybucyjnymi pamiętaj o tych ograniczeniach:

  • Maksymalny obsługiwany wymiar osadzania to 2048. Aby przechowywać większe indeksy, użyj redukcji wymiarowości.
  • Maksymalna liczba dokumentów zwracanych w odpowiedzi na zapytanie o najbliższych sąsiadów to 1000.
  • Wyszukiwanie wektorowe nie obsługuje odbiorników migawek w czasie rzeczywistym.
  • Wyszukiwanie wektorowe jest obsługiwane tylko przez biblioteki klienta w językach Python, Node.js, Go i Java.

Co dalej?