Skip to main content

Download notebook · View source · Open in Colab

Vector Search Quickstart

ApertureDB stores vectors as Descriptors within a named DescriptorSet. The set defines their dimensions, index engine, and distance metric. FindDescriptor compares a query vector with the stored vectors and returns nearby matches.

This example uses small synthetic vectors to show ingestion, KNN search, and the Python SDK wrapper without downloading an embedding model.

The examples require Python 3.10 or later, an ApertureDB connection profile or environment credentials, and permission to create, read, update, and delete objects. A unique documentation_run property separates the example data from other records. Cleanup uses that value, so keep the setup variables available until cleanup completes.

Connect to ApertureDB​

ApertureDB Cloud The SDK connects with an API key from your Cloud instance. Obtain the key from Connect > Generate API Key and add it to a .env file in this directory:

APERTUREDB_KEY=your_key_here

Local evaluation: follow the deployment guide, then configure your connection profile.

See client configuration options for all connection methods and server setup options for deployment choices.

%pip install --quiet "aperturedb==0.5.1" "python-dotenv==1.1.1"
Note: you may need to restart the kernel to use updated packages.
# Option A: ApertureDB Cloud
from dotenv import load_dotenv
load_dotenv() # loads APERTUREDB_KEY from .env into the environment
False
# For local evaluation, configure the profile from your versioned deployment package.
# https://docs.aperturedata.io/administration/deployment/DockerCompose
from aperturedb.CommonLibrary import create_connector

client = create_connector()

import uuid

documentation_run = uuid.uuid4().hex
ingredient_class = "CookbookIngredient_" + documentation_run
connection_class = "CookbookIngredientLink_" + documentation_run

def checked_query(query, input_blobs=None):
response, output_blobs = client.query(query, input_blobs or [])
assert isinstance(response, list) and len(response) == len(query), response
for command in response:
result = next(iter(command.values()))
assert result.get("status") == 0, response
return response, output_blobs

response, _ = checked_query([{"GetStatus": {}}])
client.print_last_response()
[
{
"GetStatus": {
"info": "OK",
"status": 0,
"system": "ApertureDB",
"version": "0.19.15"
}
}
]

Create a Vector Index​

A DescriptorSet is a named, indexed collection of vectors. All vectors in a set must have the same number of dimensions.

import uuid
SET_NAME = "recipe_search_" + uuid.uuid4().hex[:12]

checked_query([{
"AddDescriptorSet": {
"name": SET_NAME,
"dimensions": 4, # use 384, 512, 1024, etc. for real models
"engine": "Flat", # exact search; use HNSW for large-scale ANN
"metric": "CS", # cosine similarity; or "L2" for Euclidean
}
}])
client.print_last_response()
[
{
"AddDescriptorSet": {
"status": 0
}
}
]

Add Vectors​

Each Descriptor is a float32 vector plus optional metadata properties. The vector is passed as a binary blob.

import numpy as np

dishes = [
{"name": "Butter Chicken", "cuisine": "Indian", "vec": [0.9, 0.1, 0.8, 0.2]},
{"name": "Rajma Chawal", "cuisine": "Indian", "vec": [0.8, 0.2, 0.9, 0.1]},
{"name": "Ramen", "cuisine": "Japanese", "vec": [0.1, 0.9, 0.2, 0.8]},
{"name": "Sushi", "cuisine": "Japanese", "vec": [0.2, 0.8, 0.1, 0.9]},
{"name": "Focaccia", "cuisine": "Italian", "vec": [0.5, 0.5, 0.6, 0.4]},
]

for dish in dishes:
vec = np.array(dish["vec"], dtype="float32")
checked_query([{
"AddDescriptor": {
"set": SET_NAME,
"properties": {
'documentation_run': documentation_run,"name": dish["name"], "cuisine": dish["cuisine"]},
}
}], [vec.tobytes()])

print(f"Added {len(dishes)} descriptors")
Added 5 descriptors

FindDescriptor accepts a query vector and returns up to k_neighbors matches using the selected engine and distance metric. Approximate engines may return a different set of neighbors from an exact search.

query_vec = np.array([0.85, 0.15, 0.85, 0.15], dtype="float32")  # close to Indian dishes

response, _ = checked_query([{
"FindDescriptor": {
'constraints': ['$documentation_run', '==', documentation_run],
"set": SET_NAME,
"k_neighbors": 3,
"distances": True,
"results": {"all_properties": True},
}
}], [query_vec.tobytes()])

client.print_last_response()

assert response[0]["FindDescriptor"]["returned"] == 3
assert {item["name"] for item in response[0]["FindDescriptor"]["entities"][:2]} == {"Butter Chicken", "Rajma Chawal"}
[
{
"FindDescriptor": {
"entities": [
{
"_distance": 0.9966610670089722,
"_set_name": "recipe_search_bc0cb3c3694f",
"_uniqueid": "3.1.800",
"cuisine": "Indian",
"documentation_run": "747cfc3aed594adb965205cc7a3f6eca",
"name": "Rajma Chawal"
},
{
"_distance": 0.9966610074043274,
"_set_name": "recipe_search_bc0cb3c3694f",
"_uniqueid": "3.0.780",
"cuisine": "Indian",
"documentation_run": "747cfc3aed594adb965205cc7a3f6eca",
"name": "Butter Chicken"
},
{
"_distance": 0.8679417371749878,
"_set_name": "recipe_search_bc0cb3c3694f",
"_uniqueid": "3.4.860",
"cuisine": "Italian",
"documentation_run": "747cfc3aed594adb965205cc7a3f6eca",
"name": "Focaccia"
}
],
"returned": 3,
"status": 0
}
}
]

Python SDK: Descriptors Wrapper​

The Descriptors class in the Python SDK wraps the query language and adds reranking with MMR.

from aperturedb.Descriptors import Descriptors

descriptors = Descriptors(client)

# Basic similarity search — distances available
descriptors.find_similar(
set=SET_NAME,
vector=query_vec,
k_neighbors=3,
distances=True,
)
print("find_similar:")
for r in descriptors.response:
print(f" {r['name']:<20} distance={r['_distance']:.4f}")

print()

# MMR: diversify results (avoids near-duplicates)
# Note: find_similar_mmr uses blobs internally for reranking;
# _distance is not available in the output.
descriptors.find_similar_mmr(
set=SET_NAME,
vector=query_vec,
k_neighbors=3,
fetch_k=5,
lambda_mult=0.5, # 0.0 = max diversity, 1.0 = similarity only
)
print("find_similar_mmr (diversified):")
for r in descriptors.response:
print(f" {r['name']:<20} cuisine={r['cuisine']}")

assert len(descriptors.response) == 3
assert len({item["name"] for item in descriptors.response}) == 3
find_similar:
Rajma Chawal distance=0.9967
Butter Chicken distance=0.9967
Focaccia distance=0.8679

find_similar_mmr (diversified):
Rajma Chawal cuisine=Indian
Butter Chicken cuisine=Indian
Focaccia cuisine=Italian

Cleanup​

checked_query([{"DeleteDescriptorSet": {"with_name": SET_NAME}}])
client.print_last_response()
remaining, _ = checked_query([{"FindDescriptorSet": {"with_name": SET_NAME, "results": {"all_properties": True}}}])
assert remaining[0]["FindDescriptorSet"]["returned"] == 0
[
{
"DeleteDescriptorSet": {
"count": 1,
"status": 0
}
}
]

Next Steps​

The following examples generate embeddings from text, images, and video, or cover related storage and ingestion tasks:

Data typeNotebook
Text / documentsRecipe Text Search — sentence-transformer embeddings of dish descriptions
PDFWork with PDFs — chunk, embed, and search a PDF
ImagesImage Vector Search — CLIP embeddings and text-to-image search
Video framesVideo Vector Search — CLIP embeddings and text-to-frame search
Audio storageWork with Audio — store audio and query its metadata
Bulk loadingBulk Embeddings — concurrent ingestion with ParallelLoader
Hybrid searchHybrid Search — combine vector similarity with metadata filters