Every result carries a cosine similarity score in [-1, 1], raw and unrescaled: 1.0 means the vectors point the same direction, 0 means unrelated, negative means opposed. Higher is closer. By default a query returns its top_k best matches whatever their scores — no hidden cutoff — and you impose one with min_score when you'd rather have fewer, stronger results than a fixed count. There is no universally correct threshold: the score a "good match" earns depends on the embedding model that produced the vectors, so calibrate on your own data rather than copying a number from a different stack.

Watch the scores on a live query against the 5,000-film sample corpus (pip install "veep[samples]"):

from veep import VP, samples

df = samples.dataframe()

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

qv = samples.encode("a young wizard discovers magical powers")
for hit in vp.vectors.query("score-threshold-demo", vector=qv, top_k=5):
    print(f"{hit.score:.4f}  {hit.metadata['title']}")
0.5097  The Flight of Dragons
0.3391  FairyTale: A True Story
0.3298  The Godless Girl
0.3234  Smurfs: The Lost Village
0.3232  Idle Hands

A clear gap separates the top hit from the rest of the field. min_score turns that gap into a cutoff:

hits = vp.vectors.query("score-threshold-demo", vector=qv, top_k=5, min_score=0.4)
print(len(hits))
for hit in hits:
    print(f"{hit.score:.4f}  {hit.metadata['title']}")
1
0.5097  The Flight of Dragons

Why there is no universal threshold

The same pair of texts scores differently under different embedding models. Under all-MiniLM-L6-v2 (the model behind the sample corpus), strong plot-level matches land in the 0.4–0.6 range and near-duplicates above that — a 0.7 cutoff here would discard genuinely excellent results. Other model families run hotter and routinely score loose matches above 0.7. Neither is wrong; the absolute number just isn't comparable across models.

So pick a threshold empirically: run a handful of representative queries with no min_score, look at where good results stop and noise begins in your score distribution, and set the cutoff in that gap. Revisit it if you change embedding models — and consider declaring your model on the collection so it can't drift silently (see How do I keep my embedding model consistent?).

Two more things worth knowing:

vp.collections.delete("score-threshold-demo")