Embeddings and Vectorize

Create compatible embeddings and indexes

Chunk source content, generate one embedding shape, and create an index whose fixed dimensions and metric match it.

An embedding model turns text into a list of numbers, a vector. Texts with similar meaning produce vectors that sit close together. That closeness is what makes semantic search work: you search by meaning, not by matching words.

Workers AI runs embedding models like @cf/baai/bge-base-en-v1.5, which produces vectors of 768 numbers:

const result = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
  text: 'Cloudflare Workers run JavaScript at the edge',
})
// result.data[0] is a 768-number array

Vectorize stores those vectors and finds the nearest ones to a query. It is not where the original document lives. Keep the full text in D1 or R2 under a stable ID, and store only the vector plus a little metadata in the index.

The index shape is permanent

A Vectorize index has a fixed number of dimensions and a fixed distance metric. You cannot change either one later:

npx wrangler vectorize create course-lessons --dimensions=768 --metric=cosine

The dimensions must match the embedding model exactly. Every vector you insert and every vector you query with must have that shape. Switch embedding models later and you re-embed everything into a new index. The old vectors are numbers from a different space, and comparing across spaces gives you confident nonsense.

If you plan to filter queries by metadata, create the metadata index before inserting vectors. Only vectors inserted afterward are filterable on that property.

Chunk for meaning, not size

A chunk is the piece of text you embed, and it’s also what retrieval returns. So it has to make sense on its own. Splitting every 500 characters slices sentences in half. A heading with its section, or a few paragraphs about one point, retrieves far better.

Stable IDs matter for updates:

await env.VECTORS.upsert([{
  id: 'lesson:cloudflare-ai/streaming#2',
  values: embedding,
  metadata: { source: 'stream-and-handle-model-failures', revision: 3 },
}])

When the source changes, upsert under the same ID and the old vector is replaced. Generate random IDs at ingest time and every content update produces duplicate search results, with the stale version served right next to the fresh one.

Now do a small ingest. Chunk five short course lessons, store their text in D1 or R2, and index the vectors with source IDs and revision metadata. Then check yourself with one query: embed a question about one lesson and confirm the top match’s ID points at the chunk you expected.

Lesson completed