Skip to Content
Vector databaseSearch tuning
Raw

Search tuning in the Aetherfy vector database

What search tuning is in the Aetherfy vector database

Collection-level configuration in the Aetherfy vector database is fixed server-side: you cannot set index or optimizer parameters when you create a collection. What you can control is per-query behaviour, one search at a time, through a single pass-through option.

SDKOptionTypePosition
Pythonsearch_paramsOptional[Dict[str, Any]]last parameter of search()
JavaScriptsearchParamsRecord<string, unknown>field on SearchOptions

Aetherfy sends the object verbatim as the request body’s params field. It is added last, and only when present — omitting it produces a body with no params key at all, which is not the same request as one carrying an empty object.

This works against every deployed Aetherfy backend. There is no version gate and no capability negotiation.

Passing search parameters through the Aetherfy SDKs

The option is available on the Aetherfy client’s search() in both languages.

import os from aetherfy_vectors import AetherfyVectorsClient client = AetherfyVectorsClient(api_key=os.environ["AETHERFY_API_KEY"]) results = client.search( collection_name="articles", query_vector=[0.1, 0.2, 0.3, 0.4], limit=10, search_params={"hnsw_ef": 256}, ) for hit in results: print(hit.id, hit.score) client.close()
import { AetherfyVectorsClient } from 'aetherfy-vectors'; const client = new AetherfyVectorsClient({ apiKey: process.env.AETHERFY_API_KEY }); const results = await client.search('articles', [0.1, 0.2, 0.3, 0.4], { limit: 10, searchParams: { hnsw_ef: 256 }, }); for (const hit of results) { console.log(hit.id, hit.score); } client.dispose();

The equivalent request against the Aetherfy REST API puts the same object under params:

curl -s -X POST https://vectors.aetherfy.com/api/v1/collections/articles/points/search \ -H "Authorization: Bearer $AETHERFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"vector":[0.1,0.2,0.3,0.4],"limit":10,"params":{"hnsw_ef":256}}'

Tuning the graph walk with hnsw_ef on Aetherfy

The headline use of the option on Aetherfy is hnsw_ef.

A larger ef makes the HNSW graph walk visit more candidates: better results, at slightly higher cost per query. A smaller ef does the reverse — it visits fewer candidates and finishes sooner, at the cost of result quality. There is no universally correct value; it is a dial between quality and cost for your workload.

ValueEffect on an Aetherfy search
Larger than the defaultVisits more candidates — better results, slightly higher cost
OmittedUses Aetherfy’s tuned server-side default of hnsw_ef=100
Smaller than the defaultVisits fewer candidates — cheaper, lower result quality

Omit the option entirely unless you have measured a reason not to. The server-side default is tuned, and a hand-picked value that has not been compared against it is a guess.

Tuning memory-layer searches on Aetherfy

The same option is accepted by the Aetherfy memory layer, not just the raw vector client. Namespace and Thread both expose a .search() that takes search_params in Python and searchParams in JavaScript, with identical pass-through semantics: the object goes to the request body’s params field untouched.

The guidance the Aetherfy SDKs give for this layer is worth repeating verbatim: recall matters here — retrieving the right memory usually beats saving a millisecond. If you are going to move the dial for memory retrieval, move it towards visiting more candidates, not fewer.

How Aetherfy’s response cache treats search parameters

Aetherfy derives its server-side cache key from the request body bytes. Because the tuning object is part of the body, the same query issued at a different ef is a separate cache entry.

Two consequences follow, and both are safe:

SituationResult on Aetherfy
Same query, same paramsMay be served from the cache entry stored under those params
Same query, different paramsCannot hit the entry stored under the other params — it is a different key
Same query, params omitted vs {} presentDifferent bodies, therefore different keys

A params-varying call can never be answered by an entry stored under different params. There is no scenario in which Aetherfy returns you a result computed at an ef you did not ask for. The cost of varying the dial is cache-entry multiplication, not correctness.

What Aetherfy does not validate in the tuning object

Neither Aetherfy SDK inspects, validates, or translates the contents of the tuning object. Key names, value types, and value ranges are owned by the Aetherfy vector API and by Qdrant beneath it — the SDK’s only responsibility is to serialise the object into params.

This is deliberate: enumerating engine parameter names in the client would make every SDK release a compatibility treadmill behind the engine’s own schema. The cost is that a key the engine does not recognise is not rejected by the SDK. What is rejected — loudly — is a misspelling of the SDK’s own option name, which is the next section.

How unknown options fail in the Aetherfy SDKs

Both Aetherfy SDKs refuse an unrecognised option before any HTTP request is made. Neither one silently drops it. This is the distinction that makes the pass-through design safe: engine parameters are opaque, but SDK option names are closed.

SDKMechanismWhat you get
Pythonsearch() has no **kwargs sinkA native TypeError from the call itself; no request is sent
JavaScriptExplicit runtime guard on the options objectA thrown error naming the offending keys; no request is sent

The Aetherfy JavaScript guard’s message tells you exactly where the option should have gone:

search: unknown option(s): <names>. Engine-level search tuning goes in searchParams, e.g. { searchParams: { hnsw_ef: 256 } }.

The guard is not limited to search. These Aetherfy JavaScript methods are guarded:

Guarded method
client.search
client.scrollIter
Scope.search
Namespace.iter
Thread.iterHistory
import os from aetherfy_vectors import AetherfyVectorsClient client = AetherfyVectorsClient(api_key=os.environ["AETHERFY_API_KEY"]) try: # `hnsw_ef` is an engine parameter, not an SDK keyword argument. client.search("articles", query_vector=[0.1, 0.2, 0.3, 0.4], hnsw_ef=256) except TypeError as err: print("rejected before any request:", err) # The correct form. results = client.search( "articles", query_vector=[0.1, 0.2, 0.3, 0.4], search_params={"hnsw_ef": 256}, ) print(len(results)) client.close()
import { AetherfyVectorsClient } from 'aetherfy-vectors'; const client = new AetherfyVectorsClient({ apiKey: process.env.AETHERFY_API_KEY }); try { // `hnsw_ef` is an engine parameter, not a SearchOptions field. await client.search('articles', [0.1, 0.2, 0.3, 0.4], { hnsw_ef: 256, } as never); } catch (err) { console.log('rejected before any request:', err); } // The correct form. const results = await client.search('articles', [0.1, 0.2, 0.3, 0.4], { searchParams: { hnsw_ef: 256 }, }); console.log(results.length); client.dispose();

Absent, null, and empty tuning objects on Aetherfy

The three states are not equivalent, and the difference is observable in Aetherfy’s cache-key behaviour.

You passAetherfy sendsCache consequence
NothingNo params key in the bodyThe untuned entry
null or undefinedNo params key in the body — identical to omitting itThe untuned entry
{}"params": {} — the key is sentA distinct cache entry from the untuned one

If you are building the tuning object conditionally, prefer leaving it undefined over defaulting it to {}: the empty object costs you a second Aetherfy cache entry for what is otherwise the same query.

Filter syntax, which travels in a different body field, is on filtering; the full search() surface is on the SDK reference.

Last updated on