wandres.dev
VECTORIZE · base de datos vectorial

Crear un índice: dimensiones y métrica

Un índice de Vectorize se define con dos decisiones que quedan congeladas para siempre: cuántas dimensiones tiene cada vector y con qué métrica se mide la distancia entre ellos. Cómo se crea con wrangler vectorize create, por qué las dimensiones son un contrato rígido con el modelo de embeddings que elegiste, qué cambia entre cosine, euclidean y dot-product, cómo el flag preset deriva ambas de un modelo conocido, cómo se enlaza el índice al Worker y por qué cambiar de modelo obliga siempre a crear un índice nuevo y reindexar.

⏱ 15 min

Crear un índice en Vectorize es un comando de una sola línea, y esa brevedad engaña. En esa línea tomas dos decisiones —cuántas dimensiones y qué métrica— que quedan grabadas en el índice de por vida y que no describen tus datos, sino el modelo de embeddings con el que vas a trabajar. Elegirlas mal no produce un error al crear, produce un índice que funciona en apariencia y responde disparates, o que rechaza tus vectores el día que intentas insertarlos. Este es un caso raro y valioso en la plataforma: una configuración inmutable que te obliga a pensar antes de escribir.

🎯 Al terminar esta lección sabrás
  • Crear un índice con wrangler vectorize create fijando dimensiones y métrica.
  • Entender por qué las dimensiones son un contrato rígido con el modelo elegido.
  • Distinguir cosine, euclidean y dot-product y saber cuál corresponde a tu modelo.
  • Enlazar el índice al Worker con un binding y verificar su configuración.

El comando y sus dos decisiones

La creación vive en Wrangler, no en el código. Le das un nombre único en tu cuenta, el número de dimensiones y la métrica de distancia, y el índice queda provisionado y vacío, listo para recibir vectores.

# el nombre admite hasta 64 bytes y debe ser unico en la cuenta
npx wrangler vectorize create guia-docs --dimensions=768 --metric=cosine

# comprobar que existe y con que configuracion quedo
npx wrangler vectorize get guia-docs
npx wrangler vectorize info guia-docs

Hay un atajo que evita el error más común. Si vas a usar un modelo conocido, el flag --preset deriva por ti las dimensiones y la métrica correctas a partir del nombre del modelo, de modo que no puedas equivocarte al transcribir un número. Es la vía recomendada siempre que tu modelo figure entre los presets disponibles.

# el preset fija dimensiones y metrica segun el modelo
npx wrangler vectorize create guia-docs --preset="@cf/baai/bge-base-en-v1.5"

Los dos comandos de inspección no son intercambiables. get te devuelve la configuración declarada —dimensiones, métrica, descripción—, mientras que info añade el estado real del índice, como el número de vectores que contiene y cuándo se procesó la última mutación. El primero responde “cómo lo definí”; el segundo, “qué hay dentro ahora mismo”.

El nombre merece más cuidado del que parece. Como no puede cambiarse y va a aparecer en el manifiesto de todos tus entornos, conviene que codifique aquello que de verdad distingue un índice de otro: el corpus y, sobre todo, el modelo con el que se embebió. Un nombre como docs-es-768 te recordará dentro de seis meses por qué existe; uno como indice-2 te obligará a averiguarlo. Y como el día que cambies de modelo tendrás que convivir un tiempo con dos índices en paralelo, agradecerás que sus nombres se distingan solos.

# el inventario de indices de la cuenta, util cuando conviven varias generaciones
npx wrangler vectorize list

Las dimensiones son un contrato

El número de dimensiones no lo eliges tú: lo dicta el modelo de embeddings. Si @cf/google/embeddinggemma-300m produce vectores de 768 valores, tu índice tiene que declarar 768 y ni uno más. No es una sugerencia ni un valor por defecto que el sistema ajuste: es un contrato que Vectorize verifica en cada inserción y que rechaza si no cuadra.

Modelo de Workers AI Dimensiones Métrica natural
@cf/baai/bge-small-en-v1.5 384 cosine
@cf/google/embeddinggemma-300m 768 cosine
@cf/qwen/qwen3-embedding-0.6b 1024 cosine
@cf/baai/bge-large-en-v1.5 1024 cosine

La verificación es estricta y ocurre en cada escritura. Un vector cuya longitud no coincida con las dimensiones declaradas se rechaza con un error, lo cual es una buena noticia: es un fallo ruidoso y temprano, no una degradación silenciosa. El error de verdad peligroso es el otro, el que no se detecta, y es usar un modelo distinto que casualmente produce el mismo número de dimensiones. El índice acepta esos vectores sin protestar porque la forma encaja, pero el espacio es otro, y el sistema empieza a devolver resultados incoherentes sin que nada falle.

// comprobacion defensiva antes de escribir un lote
const { dimensions } = await env.VECTORIZE.describe();
if (embedding.length !== dimensions) {
  throw new Error("el modelo no corresponde a este indice");
}

El techo actual es de 1536 dimensiones con precisión de 32 bits por componente, lo que acomoda a la práctica totalidad de los modelos de texto en circulación. Y conviene no leer más dimensiones como sinónimo de mejor: un vector más largo captura matices más finos, pero ocupa más, consume más ancho de banda en cada consulta y encarece cada comparación. La elección real no es cuántas dimensiones quieres, sino qué modelo se ajusta a tu dominio y a tu idioma, y las dimensiones vienen dadas de propina.

⚠️
Dimensiones y métrica son inmutables

No existe un comando para cambiar las dimensiones o la métrica de un índice ya creado, y no lo hay porque no tendría sentido: ambos parámetros determinan la estructura interna con la que se organizó el espacio. Si cambias de modelo de embeddings, o si te equivocaste al declarar, el único camino es crear un índice nuevo con la configuración correcta, reindexar todo el corpus y apuntar el binding al nuevo. Planifica ese trasvase antes de llenar el índice con millones de vectores, no después.

La métrica decide qué significa parecido

La métrica define la función con la que se compara tu vector de consulta contra los vectores almacenados, y por tanto define qué entiende el índice por parecido. Las tres opciones responden a geometrías distintas y no son cuestión de gusto: cada modelo se entrena optimizando una de ellas, y usar otra degrada la calidad de la recuperación de forma silenciosa.

📐

cosine

Mide el ángulo e ignora la longitud del vector. Es la elección por defecto para texto y la que esperan casi todos los modelos de embeddings modernos.

📏

euclidean

Mide la distancia en línea recta entre los dos puntos. Encaja cuando la magnitud del vector es información legítima y no ruido.

✖️

dot-product

Proyecta un vector sobre otro y crece con la magnitud. Propio de modelos entrenados con ese objetivo, habituales en sistemas de recomendación.

La métrica no solo cambia el orden de los resultados, cambia la escala del número que los acompaña. Un score de coseno y una distancia euclídea no viven en el mismo rango ni se leen en el mismo sentido —en una, más alto es mejor; en la otra, más bajo—, así que cualquier umbral que introduzcas en tu código está atado a la métrica del índice. Es otra razón para no tratar estos valores como magnitudes universales trasladables de un sistema a otro.

La regla práctica es sencilla y aburrida: consulta la ficha de tu modelo y usa la métrica que declara. Cuando el modelo normaliza sus salidas a longitud unitaria —lo que hacen casi todos los de la familia BGE— cosine y dot-product producen el mismo orden de resultados, así que el debate se vuelve académico. El error de verdad es elegir euclidean para un modelo pensado para coseno y descubrir meses después que las respuestas son mediocres sin saber por qué.

Enlazar el índice al Worker

Un índice creado es infraestructura inerte hasta que un Worker lo alcanza, y eso ocurre con un binding declarado en el manifiesto, igual que con KV, D1 o R2. El nombre del binding es el que verás en env; el index_name es el que le diste a Wrangler al crearlo.

{
  "vectorize": [
    {
      "binding": "VECTORIZE",
      "index_name": "guia-docs"
    }
  ]
}

Nada impide declarar varios bindings a la vez, y precisamente esa posibilidad es la que hace llevadera una migración de modelo: durante el trasvase, el Worker ve el índice viejo y el nuevo al mismo tiempo, escribes en ambos, comparas resultados y solo al final retiras el que sobra.

Hay un detalle de desarrollo local que ahorra confusión. A diferencia de KV o D1, que ofrecen una emulación local completa, un índice de Vectorize no se simula en tu máquina: las operaciones se dirigen al índice real de tu cuenta. Eso significa que conviene tener un índice separado para desarrollo, declarado en un entorno propio del manifiesto, para no ensuciar el de producción con vectores de prueba que después habrá que localizar y borrar.

Tras tocar el manifiesto, ejecuta wrangler types para que env.VECTORIZE quede tipado y el editor conozca los métodos disponibles. Desde el Worker puedes pedirle al propio índice que te recuerde su configuración, algo especialmente útil como comprobación defensiva al arrancar un proceso de indexación.

// describe devuelve la configuracion real del indice
const detalles = await env.VECTORIZE.describe();
console.log(detalles.dimensions, detalles.processedUpToMutation);
flowchart LR
MOD[modelo de embeddings] --> DIM[dimensiones fijas]
MOD --> MET[metrica recomendada]
DIM --> IDX[wrangler vectorize create]
MET --> IDX
IDX --> BIND[binding en el manifiesto]
BIND --> W[env.VECTORIZE en el Worker]
style MOD fill:#89b4fa,color:#11111b
style IDX fill:#cba6f7,color:#11111b

Un apunte de capacidad para cerrar el cuadro. Una cuenta de pago admite decenas de miles de índices y cada índice hasta diez millones de vectores, cifras que en la práctica sacan la escala de la lista de preocupaciones. Eso libera una decisión de diseño que de otro modo estaría constreñida: puedes permitirte índices separados por corpus, por idioma o por generación de modelo en lugar de amontonarlo todo en uno, y esa separación suele simplificar mucho más de lo que cuesta.

Lee el diagrama de izquierda a derecha y verás que la cadena entera nace en el modelo. No eliges dimensiones y luego buscas un modelo que encaje: eliges el modelo por su calidad en tu idioma y tu dominio, y él te dicta el resto. Invertir ese orden es la fuente más habitual de índices mal configurados.

Una configuración irreversible es una forma de honestidad arquitectónica

Vivimos rodeados de sistemas que prometen que todo se puede cambiar después: añade una columna, altera un esquema, migra en caliente. Vectorize elige lo contrario y congela dos parámetros en el instante de la creación, y esa rigidez, lejos de ser una limitación por implementar, es la manifestación honesta de una verdad estructural. Las dimensiones y la métrica no son metadatos del índice, son los ejes sobre los que se construyó la partición del espacio que hace posible encontrar vecinos sin recorrerlo entero. Cambiarlas no es actualizar una fila, es afirmar que la geometría era otra, y toda organización previa queda sin fundamento. La lección trasciende a Vectorize porque nombra una distinción que conviene saber hacer en cualquier sistema: hay parámetros que son ajustes y hay parámetros que son axiomas. Un ajuste describe una preferencia y admite revisión; un axioma es un supuesto del que dependen todas las estructuras derivadas, y revisarlo equivale a reconstruirlas. Los sistemas maduros distinguen ambos y se niegan a fingir que un axioma es un ajuste, porque esa mentira piadosa solo aplaza el desastre hasta que hay diez millones de vectores dentro. Hay algo más profundo todavía: la inmutabilidad hace visible un acoplamiento que de otro modo quedaría oculto. Tu índice no es una pieza autónoma que guarda números, es la contraparte de un modelo concreto, y ese modelo es una dependencia tan real como una librería en tu lockfile, con la diferencia de que no aparece en ningún manifiesto. El día que quieras cambiar de modelo —porque salga uno mejor, porque el tuyo se retire, porque necesites otro idioma— descubrirás que arrastra contigo el índice entero. Aceptar eso desde el primer comando, y diseñar el proceso de reindexado antes de necesitarlo, es lo que separa un prototipo de un sistema que puede evolucionar.

⚔️ Define tu espacio
  1. Elige un modelo de embeddings de Workers AI, localiza sus dimensiones y su métrica recomendada, y crea el índice correspondiente con wrangler vectorize create.
  2. Crea un segundo índice usando --preset con ese mismo modelo y compara con get que la configuración resultante coincide con la que declaraste a mano.
  3. Enlaza el índice en el manifiesto, ejecuta wrangler types y llama a describe desde el Worker para confirmar dimensiones y métrica en tiempo de ejecución.
  4. Escribe el plan de migración que seguirías si mañana cambiases de modelo de embeddings, enumerando cada paso desde el índice nuevo hasta el cambio de binding.