Buscar con embeddings de vectores

En esta página, se muestra cómo usar Cloud Firestore para realizar búsquedas de vectores con K-vecino más cercano (KNN) con las siguientes técnicas:

  • Almacena valores de vectores
  • Crea y administra índices de vectores de KNN
  • Realiza una consulta de K-vecino más cercano (KNN) con una de las funciones de distancia vectorial compatibles.

Almacena embeddings de vectores

Puedes crear valores vectoriales, como incorporaciones de texto, a partir de tus datos de Cloud Firestore y almacenarlos en documentos de Cloud Firestore.

Operación de escritura con incorporación vectorial

En el siguiente ejemplo, se muestra cómo almacenar una incorporación vectorial en un documento de Cloud Firestore:

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([1.0 , 2.0, 3.0])
}

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])
});
    

Calcula incorporaciones de vectores con una Cloud Function

Para calcular y almacenar incorporaciones vectoriales cada vez que se actualiza o crea un documento, puedes configurar una función de 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,
  });
}
    

Crea y administra índices vectoriales

Antes de poder realizar una búsqueda de vecino más cercano con tus incorporaciones vectoriales, debes crear un índice correspondiente. En los siguientes ejemplos, se muestra cómo crear y administrar índices vectoriales.

Cómo crear un índice vectorial de campo único

Para crear un índice vectorial de campo único, usa gcloud alpha firestore indexes composite create:

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

Donde:

  • collection-group es el ID del grupo de colecciones.
  • vector-field es el nombre del campo que contiene la incorporación vectorial.
  • database-id es el ID de la base de datos.
  • vector-configuration incluye el vector dimension y el tipo de índice. El dimension es un número entero hasta 2,048. El tipo de índice debe ser flat. Dale formato a la configuración de índices de la siguiente manera: {"dimension":"DIMENSION", "flat": "{}"}.

Cómo crear un índice vectorial compuesto

En el siguiente ejemplo, se crea un índice vectorial compuesto para el campo color y un campo de incorporación vectorial.

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

Enumera todos los índices vectoriales

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

Reemplaza database-id por el ID de la base de datos.

Borra un índice vectorial

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

Donde:

  • index-id es el ID del índice que se borrará. Usa indexes composite list para recuperar el ID de índice.
  • database-id es el ID de la base de datos.

Describir un índice vectorial

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

Donde:

  • index-id es el ID del índice que se describirá. Usa o indexes composite list para recuperar el ID de índice.
  • database-id es el ID de la base de datos.

Haz una consulta sobre el vecino más cercano

Puedes realizar una búsqueda de similitud para encontrar los vecinos más cercanos de una incorporación vectorial. Las búsquedas de similitud requieren índices vectoriales. Si no existe un índice, Cloud Firestore sugiere crear un índice con gcloud CLI.

Python
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure

collection = collection("coffee-beans")

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

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

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

Distancias vectoriales

Las consultas de vecino más cercano admiten las siguientes opciones para la distancia vectorial:

  • EUCLIDEAN: Mide la distancia de EUCLIDEAN entre los vectores. Para obtener más información, consulta Euclidean.
  • COSINE: Compara vectores según el ángulo entre ellos, lo que te permite medir una similitud que no se basa en la magnitud de los vectores. Recomendamos usar DOT_PRODUCT con vectores normalizados por unidad en lugar de la distancia de COSINE, que es matemáticamente equivalente con un mejor rendimiento. Para obtener más información, consulta Similitud coseno.
  • DOT_PRODUCT: Es similar a COSINE, pero se ve afectado por la magnitud de los vectores. Para obtener más información, consulta Producto de Dot.

Aplica un filtro previo a los datos

Para filtrar previamente los datos antes de encontrar los vecinos más cercanos, puedes combinar una búsqueda de similitud con otros filtros, excepto los filtros de desigualdad. Se admiten los filtros compuestos and y or. Para los filtros de campo, se admiten los siguientes filtros:

  • == igual que
  • in
  • array_contains
  • array_contains_any
Python
# Similarity search with pre-filter
# Requires composite vector index
collection.where("color", "==", "red").find_nearest(
   vector_field="embedding_field",
   query_vector=Vector([3.0, 1.0, 2.0]),
   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("embedding_field", FieldValue.vector([3.0, 1.0, 2.0]), {
    limit: 5,
    distanceMeasure: "EUCLIDEAN",
  });

vectorQueryResults = await preFilteredVectorQuery.get();
    

Limitaciones

Cuando trabajes con incorporaciones vectoriales, ten en cuenta las siguientes limitaciones:

  • La dimensión de incorporación máxima admitida es 2,048. Para almacenar índices más grandes, usa la reducción de dimensiones.
  • La cantidad máxima de documentos que se pueden mostrar a partir de una consulta de vecino más cercano es de 1,000.
  • La búsqueda de vectores no admite objetos de escucha de instantáneas en tiempo real.
  • No puedes usar filtros de desigualdad para filtrar previamente los datos.
  • Solo las bibliotecas cliente de Python y Node.js admiten la búsqueda de vectores.

¿Qué sigue?