Skip to Content
ExamplesFirst collection: upsert & search
Raw

First collection: upsert and search

The smallest useful Aetherfy program: create a collection, write points into it, and search it. Both versions below are complete files. The vectors are written out as literal floats so the example runs with no embedding model installed; in production you would produce them with an embedding model — see Bring your own embeddings.

Before you start, install the SDK and export a key from https://app.aetherfy.com/dashboard/settings/api-keys :

export AETHERFY_API_KEY="afy_live_your_key_here"

Creating an Aetherfy collection in Python

Install with pip install aetherfy-vectors (Python >= 3.9).

VectorConfig and DistanceMetric are not re-exported from the package root, so import them from aetherfy_vectors.models.

from aetherfy_vectors import AetherfyVectorsClient from aetherfy_vectors.models import VectorConfig, DistanceMetric, Point # Reads AETHERFY_API_KEY from the environment. timeout is in seconds. client = AetherfyVectorsClient(timeout=30.0) collection = client.create_collection( "products", VectorConfig(size=4, distance=DistanceMetric.COSINE), description="Product catalogue for the storefront", ) print(collection.name) # products print(collection.config.size) # 4 print(collection.config.distance) # DistanceMetric.COSINE

size is the dimension of every vector you will store in the collection and cannot be changed later. It must match the output dimension of whatever produces your vectors.

Re-running this file is safe. On Aetherfy, re-creating a collection with the same configuration is idempotent and returns 200, so the script calls create unconditionally rather than guarding it with collection_exists. Only a create that asks for a different configuration under a name already in use fails, with 409 COLLECTION_NAME_TAKEN.

Upserting points into an Aetherfy collection in Python

points = [ Point(id=1, vector=[0.10, 0.20, 0.30, 0.40], payload={"title": "Wireless mouse", "category": "electronics", "price": 24.99}), Point(id=2, vector=[0.15, 0.18, 0.33, 0.41], payload={"title": "Mechanical keyboard", "category": "electronics", "price": 89.00}), Point(id=3, vector=[0.90, 0.10, 0.05, 0.02], payload={"title": "Ceramic mug", "category": "kitchen", "price": 12.50}), ] ok = client.upsert("products", points) print(ok) # True

upsert returns a plain bool in the Aetherfy Python SDK — it does not return the written points. Writing a point with an id that already exists replaces it.

Searching an Aetherfy collection in Python

results = client.search( "products", query_vector=[0.12, 0.19, 0.31, 0.40], limit=2, with_payload=True, ) for r in results: print(r.id, r.payload) # 1 {'title': 'Wireless mouse', 'category': 'electronics', 'price': 24.99} # 2 {'title': 'Mechanical keyboard', 'category': 'electronics', 'price': 89.0} client.close()

Each element is a SearchResult dataclass with the fields listed in the shape table below. score is a float produced by the distance metric you chose for the collection; its magnitude depends entirely on your vectors, so do not hard-code a threshold without measuring against your own data.

Creating an Aetherfy collection in JavaScript

Install with npm install aetherfy-vectors (Node >= 20). Import everything from the package root; there is no aetherfy-vectors/… subpath export.

Save this as main.mjs, or set "type": "module" in your package.json.

import { AetherfyVectorsClient, DistanceMetric } from 'aetherfy-vectors'; // Reads AETHERFY_API_KEY from the environment. timeout is in milliseconds. const client = new AetherfyVectorsClient({ timeout: 30000 }); async function main() { const collection = await client.createCollection( 'products', { size: 4, distance: DistanceMetric.COSINE }, 'Product catalogue for the storefront', ); console.log(collection.name); // products console.log(collection.config.size); // 4 console.log(collection.config.distance); // Cosine } main();

Note the argument order difference: the Aetherfy JS createCollection(collectionName, vectorsConfig, description?, regions?) takes the description third and has no separate distance parameter — the distance lives inside the vectors config. The Python create_collection(collection_name, vectors_config, distance=None, description=None, regions=None) accepts an optional standalone distance.

Upserting points into an Aetherfy collection in JavaScript

import { AetherfyVectorsClient } from 'aetherfy-vectors'; const client = new AetherfyVectorsClient(); async function main() { const ok = await client.upsert('products', [ { id: 1, vector: [0.10, 0.20, 0.30, 0.40], payload: { title: 'Wireless mouse', category: 'electronics', price: 24.99 } }, { id: 2, vector: [0.15, 0.18, 0.33, 0.41], payload: { title: 'Mechanical keyboard', category: 'electronics', price: 89.00 } }, { id: 3, vector: [0.90, 0.10, 0.05, 0.02], payload: { title: 'Ceramic mug', category: 'kitchen', price: 12.50 } }, ]); console.log(ok); // true } main();

upsert resolves to a boolean in the Aetherfy JS SDK, matching the Python behaviour.

Searching an Aetherfy collection in JavaScript

Everything after the query vector goes in a single options object, unlike the positional/keyword mix in Python.

import { AetherfyVectorsClient } from 'aetherfy-vectors'; const client = new AetherfyVectorsClient(); async function main() { const results = await client.search('products', [0.12, 0.19, 0.31, 0.40], { limit: 2, withPayload: true, }); for (const r of results) { console.log(r.id, r.payload); } // 1 { title: 'Wireless mouse', category: 'electronics', price: 24.99 } // 2 { title: 'Mechanical keyboard', category: 'electronics', price: 89 } client.dispose(); } main();

The full Aetherfy search options

Python keywordJavaScript option keyDefaultMeaning
limitlimit10Maximum number of results
offsetoffset0Skip this many results
query_filterqueryFilterNone / undefinedPayload filter, see Filtered search
with_payloadwithPayloadTrue / trueInclude the stored payload
with_vectorswithVectorsFalse / falseInclude the stored vector
score_thresholdscoreThresholdNone / undefinedDrop results below this score
search_paramssearchParamsNone / undefinedEngine-level search tuning, see Search tuning

The Aetherfy response shapes

These are the model definitions, not a sample. Python uses snake_case dataclasses; JavaScript uses camelCase interfaces.

Point — what you send to upsert:

FieldPython typeJavaScript typeRequired
idUnion[str, int]string | numberyes
vectorList[float]number[]yes
payloadOptional[Dict]Record<string, unknown> (optional)no

SearchResult — what search returns:

FieldPython typeJavaScript typePresent when
idUnion[str, int]string | numberalways
scorefloatnumberalways
payloadOptional[Dict]optionalwith_payload / withPayload is true
vectorOptional[List[float]]optionalwith_vectors / withVectors is true

Collection — what create_collection and get_collection return. The point-count field is named differently in each SDK.

Python fieldJavaScript fieldType
namenamestring
configconfigVectorConfigsize (int) and distance
descriptiondescriptionstring, optional
points_countpointsCountnumber, optional; None/undefined when the server does not report it
statusstatusoptional
regionsregionsoptional; only populated on a plan with more than one region

The Aetherfy distance metrics

DistanceMetric is an enum in both SDKs. The wire values are capitalised, and one of them is not what Aetherfy stores.

Enum memberWire value sentValue Aetherfy stores and returns
DistanceMetric.COSINECosineCosine
DistanceMetric.EUCLIDEANEuclideanEuclid
DistanceMetric.DOTDotDot
DistanceMetric.MANHATTANManhattanManhattan

EUCLIDEAN does not round-trip. Aetherfy normalises distance names case-insensitively and stores both euclid and euclidean as Euclid, so a collection created with DistanceMetric.EUCLIDEAN reads back config.distance == "Euclid" — comparing it to 'Euclidean' silently fails. Cosine, Dot and Manhattan come back unchanged.

Pick the metric your embedding model was trained for. Cosine is the usual choice for normalised text embeddings.

Point id rules in Aetherfy

Both Aetherfy SDKs validate ids client-side, before any request goes out. Only two forms are accepted:

FormAcceptedExample
Unsigned integer, 0 <= id <= 2**53 - 1yes1, 9007199254740991
Canonical UUID stringyes"3f1c2a7e-9b1e-4f2a-8c5d-1a2b3c4d5e6f"
UUID without hyphens (32 hex)yes"3f1c2a7e9b1e4f2a8c5d1a2b3c4d5e6f"
Braced UUIDyes"{3f1c2a7e-9b1e-4f2a-8c5d-1a2b3c4d5e6f}"
URN UUIDyes"urn:uuid:3f1c2a7e-9b1e-4f2a-8c5d-1a2b3c4d5e6f"
Arbitrary slugno"memory-001" raises ValidationError
Negative integerno-1 raises ValidationError
Boolean (Python)noTrue is rejected, it is not treated as 1

There is no coercion in either direction: an id you sent as an integer comes back as an integer, and an id you sent as a UUID string comes back as a string. If your domain keys are slugs such as order-12345, store the slug in the payload and use a UUID or an integer as the point id.

# Storing a domain key that is not a legal point id. import uuid client.upsert("products", [ Point( id=str(uuid.uuid4()), vector=[0.10, 0.20, 0.30, 0.40], payload={"sku": "memory-001", "title": "Wireless mouse"}, ) ])
// The JavaScript equivalent, using the built-in crypto.randomUUID(). await client.upsert('products', [ { id: crypto.randomUUID(), vector: [0.10, 0.20, 0.30, 0.40], payload: { sku: 'memory-001', title: 'Wireless mouse' }, }, ]);

Where Aetherfy puts this collection

Collection placement depends on your plan. On the Free and Starter plans a collection lives in a single region. Replication across more than one region begins at the plan named Performance, and only then does the regions argument to create_collection / createCollection and the regions field on Collection carry more than one entry. See Regions & replication.

Next steps with Aetherfy

Last updated on