Storage

Querying Vectors

Perform similarity search and retrieve vectors using JavaScript SDK or PostgreSQL.


Vector similarity search finds vectors most similar to a query vector using distance metrics. You can query vectors using the JavaScript SDK or directly from Postgres using SQL.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { createClient } from '@supabase/supabase-js'const supabase = createClient('https://your-project.supabase.co', 'your-service-key')const index = supabase.storage.vectors.from('embeddings').index('documents-openai')// Query with a vector embeddingconst { data, error } = await index.queryVectors({ queryVector: { float32: [0.1, 0.2, 0.3 /* ... embedding of 1536 dimensions ... */], }, topK: 5, returnDistance: true, returnMetadata: true,})if (error) { console.error('Query failed:', error)} else { // Results are ranked by similarity (lowest distance = most similar) data.vectors.forEach((result, rank) => { console.log(`${rank + 1}. ${result.metadata?.title}`) console.log(` Similarity score: ${result.distance.toFixed(4)}`) })}

Find documents similar to a query by embedding the query text:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { createClient } from '@supabase/supabase-js'import OpenAI from 'openai'const supabase = createClient(...)const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })async function semanticSearch(query, topK = 5) { // Embed the query const queryEmbedding = await openai.embeddings.create({ model: 'text-embedding-3-small', input: query }) const queryVector = queryEmbedding.data[0].embedding // Search for similar vectors const { data, error } = await supabase.storage.vectors .from('embeddings') .index('documents-openai') .queryVectors({ queryVector: { float32: queryVector }, topK, returnDistance: true, returnMetadata: true }) if (error) { throw error } return data.vectors.map((result) => ({ id: result.key, title: result.metadata?.title, similarity: 1 - result.distance, // Convert distance to similarity (0-1) metadata: result.metadata }))}// Usageconst results = await semanticSearch('How do I use vector search?')results.forEach((result) => { console.log(`${result.title} (${(result.similarity * 100).toFixed(1)}% similar)`)})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const index = supabase.storage.vectors .from('embeddings') .index('documents-openai')// Search with metadata filterconst { data } = await index.queryVectors({ queryVector: { float32: [...embedding...] }, topK: 10, filter: { // Filter by metadata fields category: 'electronics', in_stock: true, price: { $lte: 500 } // Less than or equal to 500 }, returnDistance: true, returnMetadata: true})

Retrieving specific vectors

1
2
3
4
5
6
7
8
9
10
11
12
13
const index = supabase.storage.vectors.from('embeddings').index('documents-openai')const { data, error } = await index.getVectors({ keys: ['doc-1', 'doc-2', 'doc-3'], returnData: true, returnMetadata: true,})if (!error) { data.vectors.forEach((vector) => { console.log(`${vector.key}: ${vector.metadata?.title}`) })}

Listing vectors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const index = supabase.storage.vectors.from('embeddings').index('documents-openai')let nextToken = undefinedlet pageCount = 0do { const { data, error } = await index.listVectors({ maxResults: 100, nextToken, returnData: false, // Don't return embeddings for faster response returnMetadata: true, }) if (error) break pageCount++ console.log(`Page ${pageCount}: ${data.vectors.length} vectors`) data.vectors.forEach((vector) => { console.log(` - ${vector.key}: ${vector.metadata?.title}`) }) nextToken = data.nextToken} while (nextToken)

Hybrid search: Vectors + relational data

Combine similarity search with SQL filtering and joins:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
async function hybridSearch(queryVector, filters) { const index = supabase.storage.vectors.from('embeddings').index('documents-openai') // Get similar vectors with filters const { data: vectorResults } = await index.queryVectors({ queryVector: { float32: queryVector }, topK: 100, filter: filters, returnDistance: true, returnMetadata: true, }) // Get additional details from relational database const { data: details } = await supabase .from('documents') .select('*') .in( 'id', vectorResults.vectors.map((v) => v.metadata?.doc_id) ) // Merge results return vectorResults.vectors.map((vector) => { const detail = details?.find((d) => d.id === vector.metadata?.doc_id) return { ...vector, ...detail, } })}

Real-world examples

RAG (retrieval-augmented generation)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import OpenAI from 'openai'import { createClient } from '@supabase/supabase-js'async function retrieveContextForLLM(userQuery) { const supabase = createClient(...) const openai = new OpenAI() // 1. Embed the user query const queryEmbedding = await openai.embeddings.create({ model: 'text-embedding-3-small', input: userQuery }) // 2. Retrieve relevant documents const { data: vectorResults } = await supabase.storage.vectors .from('embeddings') .index('documents-openai') .queryVectors({ queryVector: { float32: queryEmbedding.data[0].embedding }, topK: 5, returnMetadata: true }) // 3. Use vectors to augment LLM prompt const context = vectorResults.vectors .map(v => v.metadata?.content || '') .join('\n\n') const response = await openai.chat.completions.create({ model: 'gpt-4', messages: [ { role: 'system', content: `Use the following context to answer the user's question:\n\n${context}` }, { role: 'user', content: userQuery } ] }) return response.choices[0].message.content}

Product recommendations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function recommendProducts(userEmbedding, topK = 5) { const supabase = createClient(...) // Find similar products const { data } = await supabase.storage.vectors .from('embeddings') .index('products-openai') .queryVectors({ queryVector: { float32: userEmbedding }, topK, filter: { in_stock: true }, returnMetadata: true }) return data.vectors.map((result) => ({ id: result.metadata?.product_id, name: result.metadata?.name, price: result.metadata?.price, similarity: 1 - result.distance }))}
1
2
3
4
5
6
7
8
// Use metadata filters to reduce search scopeconst { data } = await index.queryVectors({ queryVector, topK: 100, filter: { category: 'electronics', // Pre-filter by category },})

Next steps