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.
8 minute lesson
An embedding model turns content into numeric vectors. Texts with similar meaning land near each other in that numeric space, which is what makes semantic search possible.
Workers AI runs embedding models like @cf/baai/bge-base-en-v1.5, which produces 768-dimensional vectors:
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 searches for nearby vectors; it is not the authoritative store for the original document. Keep the full text in D1 or R2 under a stable ID, and store only vectors plus small metadata in the index.
The index shape is permanent
The Vectorize index has fixed dimensions and a distance metric that cannot be changed later:
npx wrangler vectorize create course-lessons --dimensions=768 --metric=cosine
The dimensions must match the embedding model exactly. Every inserted and query vector must match 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 produces confident nonsense.
If you plan to filter queries by metadata, create the metadata index before inserting vectors, because only vectors inserted afterward are filterable on that property.
Chunk for meaning, not size
Choose chunk boundaries that preserve useful meaning and stable source IDs. A chunk is what retrieval returns, so it has to make sense alone. Splitting every 500 characters slices sentences in half; a heading with its section, or a few paragraphs on 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. Random IDs at ingest time turn every content update into duplicate search results served next to the stale version.
Now do a small ingest: chunk five short course lessons, store their text in D1 or R2, and index 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