| name | vectorize-search |
| description | Vectorize semantic search patterns for agent memory. Use when implementing embedding generation, semantic search, memory retrieval, or working with the Vectorize API. Triggers on Vectorize, embeddings, semantic search, vector database, similarity search. |
Vectorize Semantic Search
Vectorize provides semantic search over encrypted memories. Index the plaintext (before encryption), store embeddings with record IDs.
Security Model
┌─────────────────────────────────────────────────────────────────┐
│ SEARCH FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. STORE │
│ Content → Embed (plaintext) → Store vector + record ID │
│ Content → Encrypt → Store ciphertext in D1 │
│ │
│ 2. SEARCH │
│ Query → Embed → Vectorize search → Record IDs │
│ Record IDs → Fetch from D1 → Decrypt → Return plaintext │
│ │
│ ⚠️ Vectorize stores embeddings, NOT content │
│ ⚠️ Embeddings can leak semantic information │
│ │
└─────────────────────────────────────────────────────────────────┘
Index Configuration
[[vectorize]]
binding = "VECTORIZE"
index_name = "agent-memory"
Create index via CLI:
wrangler vectorize create agent-memory \
--dimensions=768 \
--metric=cosine
Dimension depends on embedding model:
@cf/baai/bge-base-en-v1.5: 768
@cf/baai/bge-large-en-v1.5: 1024
- OpenAI
text-embedding-3-small: 1536
Generate Embeddings
Using Cloudflare AI:
async function embed(
ai: Ai,
text: string
): Promise<number[]> {
const result = await ai.run('@cf/baai/bge-base-en-v1.5', {
text: [text]
})
return result.data[0]
}
async function embedBatch(
ai: Ai,
texts: string[]
): Promise<number[][]> {
const result = await ai.run('@cf/baai/bge-base-en-v1.5', {
text: texts
})
return result.data
}
Index Memory
async function indexMemory(
vectorize: VectorizeIndex,
ai: Ai,
record: {
id: string
did: string
collection: string
content: MemoryContent // Plaintext before encryption
}
): Promise<void> {
const text = extractSearchableText(record.content)
const embedding = await embed(ai, text)
await vectorize.upsert([{
id: record.id,
values: embedding,
metadata: {
did: record.did,
collection: record.collection,
tags: record.content.tags?.join(','),
createdAt: record.content.createdAt
}
}])
}
function extractSearchableText(content: MemoryContent): string {
const parts: string[] = []
if (content.summary) parts.push(content.summary)
if (content.text) parts.push(content.text)
if (content.tags) parts.push(content.tags.join(' '))
return parts.join('\n')
}
Semantic Search
interface SearchOptions {
did?: string
collection?: string
limit?: number
minScore?: number
}
async function searchMemory(
vectorize: VectorizeIndex,
ai: Ai,
query: string,
options: SearchOptions = {}
): Promise<VectorizeMatch[]> {
const queryEmbedding = await embed(ai, query)
const filter: VectorizeFilter = {}
if (options.did) filter.did = options.did
if (options.collection) filter.collection = options.collection
const results = await vectorize.query(queryEmbedding, {
topK: options.limit || 10,
filter,
returnMetadata: true
})
const minScore = options.minScore ?? 0.5
return results.matches.filter(m => m.score >= minScore)
}
Full Search Flow
async function recallMemories(
env: Env,
identity: AgentIdentity,
query: string,
options: SearchOptions = {}
): Promise<DecryptedMemory[]> {
const matches = await searchMemory(
env.VECTORIZE,
env.AI,
query,
{ ...options, did: identity.did }
)
if (matches.length === 0) return []
const ids = matches.map(m => m.id)
const placeholders = ids.map(() => '?').join(',')
const rows = await env.DB.prepare(
`SELECT * FROM records WHERE id IN (${placeholders})`
).bind(...ids).all()
const memories: DecryptedMemory[] = []
for (const row of rows.results) {
const record = rowToRecord(row)
const content = await decryptRecord(record, identity)
const match = matches.find(m => m.id === row.id)
memories.push({
id: row.id as string,
content,
score: match?.score || 0,
metadata: match?.metadata
})
}
return memories.sort((a, b) => b.score - a.score)
}
Batch Indexing
async function batchIndex(
vectorize: VectorizeIndex,
ai: Ai,
records: Array<{ id: string; text: string; metadata: Record<string, string> }>
): Promise<void> {
const batchSize = 100
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize)
const texts = batch.map(r => r.text)
const embeddings = await embedBatch(ai, texts)
const vectors = batch.map((r, j) => ({
id: r.id,
values: embeddings[j],
metadata: r.metadata
}))
await vectorize.upsert(vectors)
}
}
Delete from Index
async function deleteFromIndex(
vectorize: VectorizeIndex,
ids: string[]
): Promise<void> {
await vectorize.deleteByIds(ids)
}
Metadata Filtering
Vectorize supports filtering on metadata:
const results = await vectorize.query(embedding, {
topK: 10,
filter: {
did: 'did:cf:abc123',
collection: 'agent.memory.note'
}
})
Wrangler Configuration
[[vectorize]]
binding = "VECTORIZE"
index_name = "agent-memory"
[ai]
binding = "AI"
References