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

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

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

Retrieving specific vectors

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

Listing vectors

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

Hybrid search: Vectors + relational data

Combine similarity search with SQL filtering and joins:

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

Real-world examples

RAG (retrieval-augmented generation)

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

Product recommendations

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

Next steps