tech/cloudflare/ai/vectorize

VECTORIZE

Cloudflare Vectorize — vector database for semantic search and RAG.

production Cloudflare Workers
improves: tech/cloudflare/ai

Cloudflare Vectorize

Vectorize is a vector database built into the Cloudflare edge. It stores float[] embeddings and supports approximate nearest-neighbour (ANN) search, filtered by metadata.

Free tier: 5M vector dimensions stored (e.g. ~6,500 vectors at 768d). Paid: $0.01 per 1M vector dimensions queried, $0.05 per 1M stored.

wrangler.toml

[[vectorize]]
binding = "SKILLS_INDEX"
index_name = "skills-embeddings"
# Create index — choose dimensions to match your embedding model
wrangler vectorize create skills-embeddings --dimensions=768 --metric=cosine
# BGE-base = 768, BGE-large = 1024, OpenAI ada-002 = 1536

Full RAG pipeline (2nth pattern)

// Step 1: Embed the user query
async function searchSkills(query: string, env: Env): Promise<string> {
  // Generate embedding for the query
  const { data } = await env.AI.run('@cf/baai/bge-base-en-v1.5', { text: [query] });
  const queryVector = data[0];

  // Step 2: Find top-K similar skill vectors
  const results = await env.SKILLS_INDEX.query(queryVector, {
    topK: 5,
    returnMetadata: 'all',
    filter: { domain: 'tech' }, // optional metadata filter
  });

  // Step 3: Build context from retrieved skills
  const context = results.matches
    .filter(m => m.score > 0.75) // similarity threshold
    .map(m => m.metadata?.content as string)
    .join('\n\n---\n\n');

  return context;
}

// Step 4: Pass context to Claude (via AI Gateway)
async function answerWithRAG(question: string, env: Env): Promise<string> {
  const context = await searchSkills(question, env);

  const message = await client.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: 1024,
    system: `Answer using this context from the 2nth.ai skills library:\n\n${context}`,
    messages: [{ role: 'user', content: question }],
  });

  return message.content[0].text;
}

Indexing skill documents

// Embed and store a SKILL.md file
async function indexSkill(skillPath: string, content: string, env: Env): Promise<void> {
  const { data } = await env.AI.run('@cf/baai/bge-base-en-v1.5', { text: [content] });

  await env.SKILLS_INDEX.upsert([{
    id: skillPath.replace(/\//g, '_'), // 'biz_erp_sage-x3'
    values: data[0],
    metadata: {
      path: skillPath,
      domain: skillPath.split('/')[0],
      content: content.slice(0, 1000), // truncated for metadata (max 10KB)
      githubUrl: `https://github.com/2nth-ai/skills/blob/main/${skillPath}/SKILL.md`,
    },
  }]);
}

// Batch upsert multiple skills at once (more efficient)
await env.SKILLS_INDEX.upsert(skillsToIndex.map(s => ({
  id: s.path.replace(/\//g, '_'),
  values: s.embedding,
  metadata: { path: s.path, domain: s.domain, content: s.excerpt },
})));

Metadata filtering

// Filter by domain and minimum score
const results = await env.SKILLS_INDEX.query(queryVector, {
  topK: 10,
  returnMetadata: 'all',
  filter: {
    domain: { $in: ['biz', 'fin'] }, // only business + finance skills
  },
});

// Filter by status
const results = await env.SKILLS_INDEX.query(queryVector, {
  topK: 5,
  filter: { status: 'production' },
});

Namespace isolation (per-client indexes)

# Separate indexes per client keeps data isolated
[[vectorize]]
binding = "CLIENT_INDEX"
index_name = "client-proximity-green-docs"

Delete and update vectors

// Delete by ID
await env.SKILLS_INDEX.deleteByIds(['biz_erp_sage-x3', 'tech_cloudflare_workers']);

// Upsert overwrites existing vectors with same ID
await env.SKILLS_INDEX.upsert([{ id: 'biz_erp_sage-x3', values: newEmbedding, metadata: {} }]);

// Get info about the index
const info = await env.SKILLS_INDEX.describe();
console.log(info.vectorsCount, info.dimensions);

Common Gotchas

See Also