Pass a filter= argument to your vector query. The database applies the predicate during the search, not as a post-processing step in your application, so you always get top_k results that are both nearest to your query vector and match the filter. Predicates use MongoDB-style operators: {"year": {"$gte": 2000}} keeps rows whose year field is at least 2000, and the shorthand {"genre": "crime"} means genre equals "crime". Any metadata field you stored alongside your vectors is filterable — no schema declaration or secondary index setup required.

Here it is end to end, using the 5,000-film sample corpus bundled with the SDK (pip install "veep[samples]"):

from veep import VP, samples

df = samples.dataframe()  # 5,000 films: id, title, year, genre, plot, vector

vp = VP.from_creds()
vp.collections.create("metadata-filter-demo", tier="hot")
vp.vectors.upsert("metadata-filter-demo", dataframe=df)

Query without a filter first:

qv = samples.encode("a small team plans an elaborate casino heist")

for hit in vp.vectors.query("metadata-filter-demo", vector=qv, top_k=3):
    print(f"{hit.score:.4f}  {hit.metadata['title']}  ({hit.metadata['year']}, {hit.metadata['genre']})")
0.4782  Ocean's Eleven  (2001, crime)
0.4048  Lookin' to Get Out  (1982, comedy)
0.3917  Ocean's 11  (1960, crime drama)

Semantic search alone reaches across eras — the 1960 Rat Pack original ranks right behind the 2001 remake. Now the same query, scoped to this century:

for hit in vp.vectors.query("metadata-filter-demo", vector=qv, top_k=3,
                            filter={"year": {"$gte": 2000}}):
    print(f"{hit.score:.4f}  {hit.metadata['title']}  ({hit.metadata['year']}, {hit.metadata['genre']})")
0.4782  Ocean's Eleven  (2001, crime)
0.3369  Ocean's Thirteen  (2007, comedy, crime)
0.3132  Ocean's Twelve  (2004, comedy, crime)

The filter changed which rows were eligible, and the vector similarity ranked what remained: the modern Ocean's trilogy, in score order.

The operator set

Eight leaf operators filter on a single field: $eq, $ne, $gt, $gte, $lt, $lte, $in (value in a list), and $nin (value not in a list). Combine them with $and, $or, and $not for compound predicates:

for hit in vp.vectors.query("metadata-filter-demo", vector=qv, top_k=3,
                            filter={"$and": [{"year": {"$gte": 2000}},
                                             {"genre": {"$ne": "comedy, crime"}}]}):
    print(f"{hit.score:.4f}  {hit.metadata['title']}  ({hit.metadata['year']}, {hit.metadata['genre']})")
0.4782  Ocean's Eleven  (2001, crime)
0.2919  Saw VI  (2009, horror)
0.2884  Inside Man  (2006, crime drama)

Every operator, with a runnable example for each, is covered in Filter syntax in depth. If you're coming from LangChain, the same filter shapes pass through the filter= kwarg of VectorPandaStore unchanged.

vp.collections.delete("metadata-filter-demo")