Overview
The ELIPS Python API provides a powerful and expressive Filter class for querying metadata associated with vectors. It supports fluent chaining of predicates, boolean combinators, and works seamlessly with both vector similarity search (seek) and insertion-order iteration (scan).
Type Aliases used in this API:
MetaValue = Union[bool, int, float, str]PayloadLike = Mapping[str, MetaValue]
Fluent Chain
The primary way to construct filters is using the fluent builder pattern. Calling Filter() creates an empty filter that matches everything. You can chain predicates by specifying a field with .field(name)and then an operation. Multiple chained conditions are implicitly AND-ed together.
.equals(value).not_equals(value).lt(value)(less than).le(value)(less than or equal).gt(value)(greater than).gte(value)(greater than or equal).one_of(values)(matches any of the values in the list).contains(substring)(metadata substring match)
import elips
# Basic fluent chain (predicates are AND-ed)
f = (
elips.Filter()
.field("category").equals("tech")
.field("score").gte(0.8)
.field("country").one_of(["US", "GB"])
)Combinators
For more complex logic, you can combine filters using boolean operators.
.and_(other): Returns a new filter combining this filter andotherwith an AND operator..or_(other): Returns a new filter combining this filter andotherwith an OR operator.Filter.not_(inner): Static method returning a filter that negates the giveninnerfilter.
# OR combinator
either = (
elips.Filter().field("tier").equals("pro")
.or_(elips.Filter().field("year").gte(2023))
)
# NOT combinator
excluded = elips.Filter.not_(
elips.Filter().field("status").equals("archived")
)Static Factories
In addition to the fluent builder, Filter provides static factories for creating single-predicate filters directly.
Filter.compare(field, op, value): Uses theelips.Comparatorenum.Filter.in_set(field, values): Equivalent tofield().one_of().Filter.has_substring(field, substring): Equivalent tofield().contains().
The Comparator enum includes: eq, ne, lt, le, gt, ge.
# Static compare with Comparator enum
f2 = elips.Filter.compare("price", elips.Comparator.lt, 100.0)
# in_set
f3 = elips.Filter.in_set("region", ["eu-west", "us-east"])
# has_substring (full-text substring match on metadata)
f4 = elips.Filter.has_substring("title", "design")Runtime Evaluation
Filters can be evaluated against regular Python dictionaries without interacting with the database. This is useful for testing or client-side filtering.
.matches(payload: dict) -> bool: Tests if the filter matches a dictionary..matches_all() -> bool: ReturnsTrueif the filter is completely empty (matches anything).
# Runtime eval without DB
record = {"category": "tech", "score": 0.9, "country": "US"}
print(f.matches(record)) # True
print(elips.Filter().matches_all()) # TrueMetadata Acceleration
ELIPS utilizes a MetadataIndex to accelerate similarity searches by narrowing down candidates before executing the Approximate Nearest Neighbor (ANN) search. Only equality constraints (.equals() and .one_of() / in_set()) can be used for index pre-filtering. Range predicates (lt, le, gt, ge) and substring matches (contains, has_substring) are applied during a post-filtering phase after ANN candidates are retrieved.
You can inspect a filter to see what constraints can be pushed down to the index:
.exact_constraints() -> Optional[list[tuple[str, list[MetaValue]]]]: Returns a list of equality constraints if acceleratable, otherwiseNone.
# Check accelerability
constraints = f.exact_constraints()
# Returns [("category", ["tech"]), ("country", ["US", "GB"])]
# The gte(0.8) predicate is NOT equality, so it's not in exact_constraintsSeek Integration
Filters are heavily used when querying a Vault. Both vector similarity search (seek) and insertion-order iteration (scan) accept a where argument.
# Use in seek
hits = vault.seek([1.0, 0.0], top=10, where=f)
# Use in scan (insertion-order iteration)
rows = vault.scan(where=f, offset=0, limit=100)Explain Seek
To understand how your filter interacts with the query planner, you can use explain_seek(). This returns a QueryPlan object detailing whether metadata acceleration was utilized.
# Explain the plan
plan = vault.explain_seek([1.0, 0.0], top=10, where=f)
print(f"Accelerated: {plan.metadata_accelerated}")
print(f"Strategy: {plan.strategy.name}")Examples
Here is an example combining complex multi-condition, OR, NOT, and tenant scoping logic:
tenant_filter = elips.Filter().field("tenant_id").equals("org_123")
active_items = (
elips.Filter()
.field("status").equals("active")
.field("visibility").one_of(["public", "internal"])
)
no_drafts = elips.Filter.not_(
elips.Filter().field("state").equals("draft")
)
# Combine them all
final_filter = tenant_filter.and_(active_items).and_(no_drafts)Pitfalls
- Chained predicates are AND-ed: Calling
.field(x).equals(y).field(a).equals(b)creates an AND filter. It does NOT overwrite or OR them. - Empty filters are not errors: An empty
elips.Filter()matches everything. This is usually intended but can be unexpected. Usematches_all()to detect empty filters if you need to validate user input. - Substring is metadata match:
.contains()andhas_substring()perform substring matches on the metadata text, not semantic searches on the document content. Useseek_textorseek_hybridfor semantic text search. - Immutability in combinators:
.or_()and.and_()return new filters. Callingf.or_(other)does not mutatef. - Acceleration limitations:
exact_constraints()returnsNoneif ANY predicate in the filter is non-equality (like ranges or substrings). The whole filter will still work correctly (and results will be accurate), but the index cannot accelerate it entirely before the ANN step.