Skip to Content
ExamplesFiltered search
Raw

Filtered search

A filter restricts a vector search to the points whose payload matches a condition. In Aetherfy the filter is a plain object with up to three clause lists — must, should, must_not — each holding condition objects.

Before you start:

export AETHERFY_API_KEY="afy_live_your_key_here"

The Aetherfy filter vocabulary

ClauseMeaning
mustEvery listed condition has to hold. Logical AND.
shouldAt least one listed condition contributes; used to express an OR-style match.
must_notNo listed condition may hold. Logical NOT.

Conditions come in two forms.

ConditionShapeMatches
Exact match{"key": "<payload key>", "match": {"value": <value>}}Points whose payload key equals the value
Range{"key": "<payload key>", "range": {"gte": …, "lte": …, "gt": …, "lt": …}}Numeric payload values inside the bounds; supply only the bounds you need

Aetherfy forwards the filter object to the underlying engine verbatim, with no key translation. That means the key names above are the literal wire names in both SDKs, and it is the reason for the JavaScript pitfall documented further down.

Setting up an Aetherfy collection to filter against

Both language sections below assume this collection exists. Vectors are literal floats so the example runs without an embedding model.

Python setup

from aetherfy_vectors import AetherfyVectorsClient from aetherfy_vectors.models import VectorConfig, DistanceMetric, Point client = AetherfyVectorsClient() client.create_collection( "catalogue", VectorConfig(size=4, distance=DistanceMetric.COSINE), ) client.upsert("catalogue", [ Point(id=1, vector=[0.10, 0.20, 0.30, 0.40], payload={"title": "Wireless mouse", "category": "electronics", "price": 24.99, "in_stock": True}), Point(id=2, vector=[0.15, 0.18, 0.33, 0.41], payload={"title": "Mechanical keyboard", "category": "electronics", "price": 189.00, "in_stock": True}), Point(id=3, vector=[0.12, 0.22, 0.29, 0.38], payload={"title": "USB-C hub", "category": "electronics", "price": 39.50, "in_stock": False}), Point(id=4, vector=[0.90, 0.10, 0.05, 0.02], payload={"title": "Ceramic mug", "category": "kitchen", "price": 12.50, "in_stock": True}), ])

JavaScript setup

import { AetherfyVectorsClient, DistanceMetric } from 'aetherfy-vectors'; const client = new AetherfyVectorsClient(); await client.createCollection('catalogue', { size: 4, distance: DistanceMetric.COSINE }); await client.upsert('catalogue', [ { id: 1, vector: [0.10, 0.20, 0.30, 0.40], payload: { title: 'Wireless mouse', category: 'electronics', price: 24.99, in_stock: true } }, { id: 2, vector: [0.15, 0.18, 0.33, 0.41], payload: { title: 'Mechanical keyboard', category: 'electronics', price: 189.00, in_stock: true } }, { id: 3, vector: [0.12, 0.22, 0.29, 0.38], payload: { title: 'USB-C hub', category: 'electronics', price: 39.50, in_stock: false } }, { id: 4, vector: [0.90, 0.10, 0.05, 0.02], payload: { title: 'Ceramic mug', category: 'kitchen', price: 12.50, in_stock: true } }, ]);

Filtered search in Aetherfy with Python

A filter goes in the query_filter keyword. You can pass a plain dictionary or the Filter dataclass — the client accepts both.

from aetherfy_vectors import AetherfyVectorsClient client = AetherfyVectorsClient() # must: electronics only. results = client.search( "catalogue", query_vector=[0.12, 0.19, 0.31, 0.40], limit=10, query_filter={ "must": [ {"key": "category", "match": {"value": "electronics"}}, ] }, ) for r in results: print(r.id, r.payload["title"]) # 1 Wireless mouse # 2 Mechanical keyboard # 3 USB-C hub # must + range: electronics under 50. results = client.search( "catalogue", query_vector=[0.12, 0.19, 0.31, 0.40], query_filter={ "must": [ {"key": "category", "match": {"value": "electronics"}}, {"key": "price", "range": {"lte": 50}}, ] }, ) for r in results: print(r.id, r.payload["title"]) # 1 Wireless mouse # 3 USB-C hub # must_not: exclude everything out of stock. results = client.search( "catalogue", query_vector=[0.12, 0.19, 0.31, 0.40], query_filter={ "must": [{"key": "category", "match": {"value": "electronics"}}], "must_not": [{"key": "in_stock", "match": {"value": False}}], }, ) for r in results: print(r.id, r.payload["title"]) # 1 Wireless mouse # 2 Mechanical keyboard # should: either category qualifies. results = client.search( "catalogue", query_vector=[0.12, 0.19, 0.31, 0.40], query_filter={ "should": [ {"key": "category", "match": {"value": "electronics"}}, {"key": "category", "match": {"value": "kitchen"}}, ] }, ) print(len(results)) # 4 client.close()

The Aetherfy Filter dataclass in Python

Filter lives in aetherfy_vectors.models, not in the package root. Its to_dict() emits the correct wire keys — must, must_not, should — so the Python path is safe either way.

from aetherfy_vectors import AetherfyVectorsClient from aetherfy_vectors.models import Filter client = AetherfyVectorsClient() f = Filter( must=[{"key": "category", "match": {"value": "electronics"}}], must_not=[{"key": "in_stock", "match": {"value": False}}], ) print(f.to_dict()) # {'must': [{'key': 'category', 'match': {'value': 'electronics'}}], # 'must_not': [{'key': 'in_stock', 'match': {'value': False}}]} results = client.search( "catalogue", query_vector=[0.12, 0.19, 0.31, 0.40], query_filter=f, ) for r in results: print(r.id, r.payload["title"]) # 1 Wireless mouse # 2 Mechanical keyboard client.close()

Filter has exactly three fields — must, must_not, should — all optional and all defaulting to None.

Filtered search in Aetherfy with JavaScript

The filter goes in the queryFilter key of the options object. Write the clause key as mustNot — the JavaScript SDK translates it to the wire key must_not for you.

import { AetherfyVectorsClient } from 'aetherfy-vectors'; const client = new AetherfyVectorsClient(); async function main() { // must: electronics only. let results = await client.search('catalogue', [0.12, 0.19, 0.31, 0.40], { limit: 10, queryFilter: { must: [{ key: 'category', match: { value: 'electronics' } }], }, }); for (const r of results) console.log(r.id, r.payload.title); // 1 Wireless mouse // 2 Mechanical keyboard // 3 USB-C hub // must + range: electronics under 50. results = await client.search('catalogue', [0.12, 0.19, 0.31, 0.40], { queryFilter: { must: [ { key: 'category', match: { value: 'electronics' } }, { key: 'price', range: { lte: 50 } }, ], }, }); for (const r of results) console.log(r.id, r.payload.title); // 1 Wireless mouse // 3 USB-C hub // mustNot: exclude everything out of stock. The SDK sends it as must_not. results = await client.search('catalogue', [0.12, 0.19, 0.31, 0.40], { queryFilter: { must: [{ key: 'category', match: { value: 'electronics' } }], mustNot: [{ key: 'in_stock', match: { value: false } }], }, }); for (const r of results) console.log(r.id, r.payload.title); // 1 Wireless mouse // 2 Mechanical keyboard // should: either category qualifies. results = await client.search('catalogue', [0.12, 0.19, 0.31, 0.40], { queryFilter: { should: [ { key: 'category', match: { value: 'electronics' } }, { key: 'category', match: { value: 'kitchen' } }, ], }, }); console.log(results.length); // 4 client.dispose(); } main();

The filter object is typed, so a Filter-annotated variable works without a cast:

import type { Filter } from 'aetherfy-vectors'; const queryFilter: Filter = { must: [{ key: 'category', match: { value: 'electronics' } }], mustNot: [{ key: 'in_stock', match: { value: false } }], }; const results = await client.search('catalogue', [0.12, 0.19, 0.31, 0.40], { queryFilter });

How each Aetherfy SDK spells the exclusion clause

The wire key is must_not. You write your SDK’s spelling and it translates:

SDKWhat you writeWhat Aetherfy sends
Pythonmust_not, or Filter(must_not=...)must_not
JavaScript / TypeScriptmustNotmust_not

Each SDK throws on the other’s spelling, and names the right one. Both validate clauses before sending:

You writePythonJavaScript
must_notacceptedthrows — “Write the clause as mustNot”
mustNotthrows — “Write the clause as must_not”accepted

Aetherfy does not validate the filter body itself, so a clause in the wrong idiom used to be forwarded and quietly do nothing — no exception, no log line, just rows you meant to exclude coming back. That failed in both languages: mustNot in JavaScript, and a plain dict carrying mustNot in Python. If you have code written against an older SDK, those filters were not filtering; if you adopted a must_not workaround in JavaScript, remove it.

A Python Filter dataclass could never carry the wrong spelling — its fields are must, must_not, should — so passing a Filter avoids the question. Every filter-taking method accepts either a Filter or a dict, count included.

Where else Aetherfy accepts a filter

The same filter object is accepted by four other Aetherfy calls, under different parameter names. The same clause vocabulary applies to every one of them.

CallPython parameterJavaScript option
searchquery_filterqueryFilter
countcount_filtercountFilter
scrollscroll_filterscrollFilter
scroll_iter / scrollIterscroll_filterscrollFilter
deletepoints_selector (pass the filter object directly)pointsSelector

For deleting by filter see Retrieve, delete, count; for paging a filtered set see Batch upsert & pagination. The filter vocabulary itself is documented in full at Filtering.

Last updated on