elips/docs
Reference · Python

Filtering

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)
python
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 and other with an AND operator.
  • .or_(other): Returns a new filter combining this filter and other with an OR operator.
  • Filter.not_(inner): Static method returning a filter that negates the given inner filter.
python
# 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 the elips.Comparator enum.
  • Filter.in_set(field, values): Equivalent to field().one_of().
  • Filter.has_substring(field, substring): Equivalent to field().contains().

The Comparator enum includes: eq, ne, lt, le, gt, ge.

python
# 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: Returns True if the filter is completely empty (matches anything).
python
# Runtime eval without DB
record = {"category": "tech", "score": 0.9, "country": "US"}
print(f.matches(record))  # True

print(elips.Filter().matches_all())  # True

Metadata 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, otherwise None.
python
# 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_constraints

Seek Integration

Filters are heavily used when querying a Vault. Both vector similarity search (seek) and insertion-order iteration (scan) accept a where argument.

python
# 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.

python
# 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:

python
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. Use matches_all() to detect empty filters if you need to validate user input.
  • Substring is metadata match: .contains() and has_substring() perform substring matches on the metadata text, not semantic searches on the document content. Use seek_text or seek_hybrid for semantic text search.
  • Immutability in combinators: .or_() and .and_() return new filters. Calling f.or_(other) does not mutate f.
  • Acceleration limitations: exact_constraints() returns None if 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.