Rechercher avec des embeddings vectoriels
Cette page explique comment utiliser Firestore pour effectuer des recherches vectorielles des k plus proches voisins (KNN) à l'aide des techniques suivantes :
- Stocker des valeurs vectorielles
- Créer et gérer des index vectoriels KNN
- Envoyer une requête des k plus proches voisins (KNN) à l'aide de l'une des mesures de distance vectorielle compatibles
Avant de commencer
Avant de stocker des embeddings dans Firestore, vous devez générer des embeddings vectoriels. Firestore ne génère pas les embeddings. Vous pouvez utiliser un service tel que Vertex AI pour créer des valeurs vectorielles, par exemple, des embeddings de texte à partir de vos données Firestore. Vous pouvez ensuite stocker ces embeddings dans des documents Firestore.
Pour en savoir plus sur les embeddings, consultez Que sont les embeddings ?
Pour découvrir comment obtenir des embeddings de texte avec Vertex AI, consultez Obtenir des embeddings de texte.
Stocker les embeddings vectoriels
Les exemples suivants montrent comment stocker des embeddings vectoriels dans Firestore.
Opération d'écriture avec un embedding vectoriel
L'exemple suivant montre comment stocker un embedding vectoriel dans un document Firestore :
Python
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
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();
Calculer des embeddings vectoriels avec une fonction Cloud
Pour calculer et stocker des embeddings vectoriels chaque fois qu'un document est mis à jour ou créé, vous pouvez configurer une fonction Cloud Run :
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
Créer et gérer des index vectoriels
Avant de pouvoir effectuer une recherche des plus proches voisins avec vos embeddings vectoriels, vous devez créer un index correspondant. Les exemples suivants montrent comment créer et gérer des index vectoriels avec Google Cloud CLI et la console. Vous pouvez également gérer des index vectoriels avec la CLI Firebase et Terraform.
Créer un index vectoriel
Google Cloud Console
Pour créer manuellement un index à partir de la Google Cloud console :
- Dans la Google Cloud console, accédez à la page Bases de données.
- Sélectionnez la base de données requise dans la liste des bases de données.
- Dans le menu de navigation, cliquez sur Index, puis sur l'onglet Manuel.
- Cliquez sur Créer un index.
Pour indexer un champ vectoriel pour les recherches vectorielles, sélectionnez Créer un index vectoriel.
-
Saisissez un ID de collection. Saisissez un chemin d'accès au champ vectoriel et le nombre de dimensions d'embedding vectoriel. Ajoutez les noms des champs supplémentaires que vous souhaitez indexer et un mode d'indexation pour chaque champ.
Cliquez sur Enregistrer l'index.
Votre nouvel index s'affiche dans la liste des index manuels et Firestore commence à le créer. Une fois que votre index est créé, une coche verte apparaît à côté de l'index.
gcloud
Avant de créer un index vectoriel, passez à la dernière version de Google Cloud CLI :
gcloud components update
Pour créer un index vectoriel, utilisez gcloud firestore indexes composite create :
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
où :
- collection-group correspond à l'ID du groupe de collections.
- vector-field correspond au nom du champ contenant l'embedding vectoriel.
- database-id correspond à l'ID de la base de données.
- vector-configuration inclut le vecteur
dimensionet le type d'index. Ladimensionest un entier allant jusqu'à 2 048. Le type d'index doit êtreflat. Mettez en forme la configuration de l'index comme suit :{"dimension":"DIMENSION", "flat": "{}"}.
L'exemple suivant crée un index composite, y compris un index vectoriel pour le champ vector-field et un index ascendant pour le champ color. Vous pouvez utiliser ce type d'index pour
préfiltrer les données avant une recherche des plus proches voisins.
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
Répertorier tous les index vectoriels
Google Cloud Console
- Dans la Google Cloud console, accédez à la page Bases de données.
- Sélectionnez la base de données requise dans la liste des bases de données.
-
Dans le menu de navigation, cliquez sur Index, puis sur l'onglet Manuel.
Le tableau des index répertorie tous les index de la base de données. Les index vectoriels incluent un champ vectoriel avec une icône .
gcloud
Pour répertorier tous les index et récupérer les ID d'index :
gcloud firestore indexes composite list --database=database-id
Remplacez database-id par l'ID de la base de données.
Vous pouvez utiliser l'ID d'index pour afficher plus de détails sur un index :
gcloud firestore indexes composite describe index-id --database=database-id
où :
- index-id correspond à l'ID de l'index à décrire.
- database-id correspond à l'ID de la base de données.
Supprimer un index vectoriel
Google Cloud Console
- Dans la Google Cloud console, accédez à la page Bases de données.
- Sélectionnez la base de données requise dans la liste des bases de données.
-
Dans le menu de navigation, cliquez sur Index, puis sur l'onglet Manuel.
- Dans la liste de vos index manuels, cliquez sur le bouton Plus de l'index que vous souhaitez supprimer. Cliquez sur Supprimer.
- Confirmez que vous souhaitez supprimer cet index en cliquant sur Supprimer l'index dans l'alerte.
gcloud
gcloud firestore indexes composite delete index-id --database=database-id
où :
- index-id correspond à l'ID de l'index à supprimer.
Utilisez
indexes composite listpour récupérer l'ID d'index. - database-id correspond à l'ID de la base de données.
Envoyer une requête des plus proches voisins
Vous pouvez effectuer une recherche de similarités pour trouver les voisins les plus proches d'un embedding vectoriel. Les recherches de similarités nécessitent des index vectoriels. Si un index n'existe pas, Firestore suggère un index à créer à l'aide de la gcloud CLI.
L'exemple suivant trouve les 10 voisins les plus proches du vecteur de requête.
Python
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
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();
Distances vectorielles
Les requêtes des plus proches voisins sont compatibles avec les options suivantes pour la distance vectorielle :
EUCLIDEAN: mesure la distanceEUCLIDEANentre les vecteurs. Pour en savoir plus, consultez la section Euclidienne.COSINE: compare les vecteurs en fonction de l'angle entre eux, ce qui vous permet de mesurer la similarité qui n'est pas basée sur l'amplitude des vecteurs. Nous vous recommandons d'utiliserDOT_PRODUCTavec des vecteurs normalisés unitaires au lieu de la distance COSINE, qui est mathématiquement équivalente avec de meilleures performances. Pour en savoir plus, consultez la section Similarité cosinus.DOT_PRODUCT: semblable àCOSINE, mais affecté par l'amplitude des vecteurs. Pour en savoir plus, consultez la section Produit scalaire.
Choisir la mesure de distance
Selon que tous vos embeddings vectoriels sont normalisés ou non, vous pouvez déterminer la mesure de distance à utiliser pour trouver la mesure de distance. Un embedding vectoriel normalisé a une amplitude (longueur) de 1,0 exactement.
De plus, si vous savez avec quelle mesure de distance votre modèle a été entraîné, utilisez cette mesure de distance pour calculer la distance entre vos embeddings vectoriels.
Données normalisées
Si vous disposez d'un ensemble de données dans lequel tous les embeddings vectoriels sont normalisés, les trois mesures de distance fournissent les mêmes résultats de recherche sémantique. En substance, bien que chaque mesure de distance renvoie une valeur différente, ces valeurs sont triées de la même manière. Lorsque les embeddings sont normalisés, DOT_PRODUCT est généralement le plus efficace en termes de calcul, mais la différence est négligeable dans la plupart des cas. Toutefois, si votre application est très sensible aux performances, DOT_PRODUCT peut vous aider à les optimiser.
Données non normalisées
Si vous disposez d'un ensemble de données dans lequel les embeddings vectoriels ne sont pas normalisés, il n'est pas mathématiquement correct d'utiliser DOT_PRODUCT comme mesure de distance, car le produit scalaire ne mesure pas la distance. Selon la façon dont les embeddings ont été générés et le type de recherche préféré, la mesure de distance COSINE ou EUCLIDEAN produit des résultats de recherche qui sont subjectivement meilleurs que les autres mesures de distance.
Il peut être nécessaire d'expérimenter avec COSINE ou EUCLIDEAN pour déterminer la meilleure option pour votre cas d'utilisation.
Vous ne savez pas si les données sont normalisées ou non
Si vous ne savez pas si vos données sont normalisées ou non et que vous souhaitez utiliser DOT_PRODUCT, nous vous recommandons d'utiliser COSINE à la place.
COSINE est semblable à DOT_PRODUCT, mais avec une normalisation intégrée.
La distance mesurée à l'aide de COSINE est comprise entre 0 et 2. Un résultat proche de 0 indique que les vecteurs sont très similaires.
Préfiltrer les documents
Pour préfiltrer les documents avant de trouver les voisins les plus proches, vous pouvez combiner une recherche de similarités avec d'autres opérateurs de requête. Les filtres composites and et or sont compatibles. Pour en savoir plus sur les filtres de champ compatibles, consultez la section Opérateurs de requête.
Python
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
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();
Récupérer la distance vectorielle calculée
Vous pouvez récupérer la distance vectorielle calculée en attribuant un nom de propriété de sortie distance_result_field à la requête FindNearest, comme illustré dans l'exemple suivant :
Python
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
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")); }
Si vous souhaitez utiliser un masque de champ pour renvoyer un sous-ensemble de champs de document avec un distanceResultField, vous devez également inclure la valeur de distanceResultField dans le masque de champ, comme illustré dans l'exemple suivant :
Python
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
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")); }
Spécifier un seuil de distance
Vous pouvez spécifier un seuil de similarité qui ne renvoie que les documents compris dans le seuil. Le comportement du champ de seuil dépend de la mesure de distance que vous choisissez :
- Les distances
EUCLIDEANetCOSINElimitent le seuil aux documents dont la distance est inférieure ou égale au seuil spécifié. Ces mesures de distance diminuent à mesure que les vecteurs deviennent plus similaires. - La distance
DOT_PRODUCTlimite le seuil aux documents dont la distance est supérieure ou égale au seuil spécifié. Les distances de produit scalaire augmentent à mesure que les vecteurs deviennent plus similaires.
L'exemple suivant montre comment spécifier un seuil de distance pour renvoyer jusqu'à 10 documents les plus proches qui se trouvent à 4,5 unités au maximum à l'aide de la métrique de distance EUCLIDEAN :
Python
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
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()); }
Limites
Lorsque vous utilisez des embeddings vectoriels, tenez compte des limites suivantes :
- La dimension d'embedding maximale acceptée est de 2 048. Pour stocker des index plus volumineux, utilisez la réduction de la dimensionnalité.
- Le nombre maximal de documents à renvoyer à partir d'une requête des plus proches voisins est de 1 000 (limite de l'édition Standard uniquement).
- La recherche vectorielle n'est pas compatible avec les écouteurs d'instantanés en temps réel.
- Seules les bibliothèques clientes Python, Node.js, Go et Java sont compatibles avec la recherche vectorielle.
Étape suivante
- Découvrez les bonnes pratiques pour Firestore.
- Comprendre les lectures et les écritures à grande échelle.