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 (collection.query) and row retrieval (collection.get).

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, gte.

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, gte) 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

Query Integration

Filters are heavily used when querying a Collection. Both vector similarity search (collection.query) and row retrieval (collection.get) accept a where argument, as does collection.delete.

python
# Use in query (vector similarity search)
hits = collection.query(query_embeddings=[1.0, 0.0], n_results=10, where=f)

# Use in get (row retrieval by predicate)
rows = collection.get(where=f, offset=0, limit=100)

# Use in delete (remove every matching record)
removed = collection.delete(where=f)

Explain Query

To understand how your filter interacts with the query planner, you can use collection.explain(). This returns a QueryPlan object detailing whether metadata acceleration was utilized.

python
# Explain the plan
plan = collection.explain([1.0, 0.0], n_results=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 collection.query(query_texts=...) (or a hybrid query_embeddings + query_texts call) 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.