Boost
Boost soft-ranks search results: it promotes or demotes matching documents without removing them from the result set. Matching documents move up. Non-matching documents stay in the results but rank lower.
Apply boost to vector, hybrid, BM25, near-text, near-vector, near-object, near-image, and near-media queries.
How it works
Section titled “How it works”A boost is a post-retrieval rescorer:
- The primary search (vector, hybrid, BM25, ...) fetches
depthcandidate results. Setdepthhigher thanoffset + limitif you want boost to consider candidates beyond the first page. - The boost scorer rescores those candidates in memory by evaluating each condition per candidate, normalizing per result set, and blending with the primary score. There are no new index queries. The cost is per-candidate in-memory scoring, not extra shard fan-out. Both primary and boost scores are min-max normalized into
[0, 1]before blending, and the final score is renormalized to[0, 1]. - The user's original
offsetandlimitare applied after the re-sort.
Condition types
Section titled “Condition types”A boost is one or more conditions, blended into a single rescore. Every condition is one of: filter, property value, time decay, or numeric decay.
Filter condition (soft WHERE)
Section titled “Filter condition (soft WHERE)”Score is 1 if the result matches the filter, 0 if not. Non-matching documents stay in the result set but rank lower than matching ones. Supported filter operators: Equal, NotEqual, GreaterThan, GreaterThanEqual, LessThan, LessThanEqual, And, Or, Not. (Like, IsNull, geo operators, and ref-path filters are not supported in boost conditions.)
# Promote articles in the "research" category without filtering others out.response = articles.query.near_text( query="transformer architectures", limit=5, boost=Boost.filter( Filter.by_property("category").equal("research"), weight=0.5, ), return_properties=["title", "category"],)for o in response.objects: print(o.properties["category"], "-", o.properties["title"])Property-value condition
Section titled “Property-value condition”Continuous score proportional to a numeric property's value (likes, downloads, popularity, ...). The raw value is optionally modified, then min-max normalized to [0, 1] across the result set.
The name argument is required, only numeric properties (int, number) are supported.
| Modifier | Effect | When to use |
|---|---|---|
NONE (default) |
score = value |
Values in a narrow range. |
LOG1P |
score = log(1 + value) |
Long-tail dampening (e.g. download counts from 5 to 5_000_000). |
SQRT |
score = sqrt(value) |
Milder long-tail dampening. |
# Bias toward articles with more `likes`. LOG1P dampens the long tail so a# single 5-million-likes outlier doesn't dominate.response = articles.query.near_text( query="transformer architectures", limit=5, boost=Boost.numeric_property( "likes", modifier=Boost.Modifier.LOG1P, weight=0.7, ), return_properties=["title", "likes"],)for o in response.objects: print(o.properties["likes"], "-", o.properties["title"])For "closer to a specific value is better" instead of "higher is better", use numeric decay below.
Time decay
Section titled “Time decay”Continuous [0, 1] score that decays with distance from an origin time. The canonical use case is "boost more recent documents".
| Parameter | Required | Notes |
|---|---|---|
property |
Yes | Name of a date property. |
origin |
No | "now" (default), a datetime, or an ISO string. |
scale |
Yes | Distance at which the score equals decay. Accepts a timedelta or duration string ("7d", "6h", "30m"). |
offset |
No | Distance below which the score is exactly 1. Default 0. |
curve |
No | EXPONENTIAL (default), GAUSSIAN, or LINEAR. See Curves below. |
decay |
No | Score at scale distance. Default 0.5. Range (0, 1]. |
# Score decays exponentially over time. "30d scale" + decay=0.5 means an# article that's 30 days old gets half the score of one published "now".response = articles.query.near_text( query="transformer architectures", limit=5, boost=Boost.time_decay( "published", origin="now", scale=timedelta(days=30), curve=Boost.Curve.EXPONENTIAL, decay=0.5, weight=0.6, ), return_properties=["title", "published"],)Numeric decay
Section titled “Numeric decay”Like time decay but for numeric (int, number) properties. Use this when "closer to X is better": prices near a target, distances near a coordinate, ages near a band. Same scale / offset / decay / curve semantics as time decay, with all values expressed as numbers.
scale must be > 0 and decay (if set) must be in (0, 1], same as time decay.
# Score peaks at a target price and falls off symmetrically. Gauss gives a# bell-shaped falloff: items within `offset` of $49.99 score 1.0, items at# $59.99 (one scale away) score `decay`.response = articles.query.near_text( query="transformer architectures", limit=5, boost=Boost.numeric_decay( "price", origin=49.99, scale=10.0, curve=Boost.Curve.GAUSSIAN, decay=0.5, weight=0.5, ), return_properties=["title", "price"],)Curves
Section titled “Curves”The three decay curves shape how score falls off with distance. At distance == 0 the score is always 1. At distance == scale the score is always exactly decay. Past scale they behave differently:
| Curve | Shape | When to use |
|---|---|---|
EXPONENTIAL (default) |
Heavy tail: score halves geometrically every scale past the origin. |
"Recency matters, but don't aggressively flatten older items to zero." |
GAUSSIAN |
Bell curve: sharp falloff past scale. |
"Items close to the origin are great, items far away are nearly worthless." |
LINEAR |
Straight line: score reaches zero at a finite distance past scale. |
"Predictable falloff with a clear cutoff." |
Only these three values are accepted. Anything else is rejected at request time.
Blending and weights
Section titled “Blending and weights”A boost must carry at least one and at most 20 conditions. Use Boost.blend(...) to combine multiple conditions into one rescore. Each condition can carry its own weight (default 1.0). The outer weight (default 0.5) controls how much the combined boost affects the final score.
final_score = (1 − weight) · primary_norm + weight · boost_normweight: the outer blending weight, in[0, 1]. Defaults to0.5.weight: 0is a no-op: the boost short-circuits and primary results are returned unchanged.- Per-condition
weight: afloatdefaulting to1.0. Use it to balance multiple boosts ("recency twice as important as popularity"). - Negative per-condition
weight: demotes matching documents. They stay in the result set but rank lower than non-matching ones.
# Combine two soft signals: recency (weight 2) + popularity (weight 1).# The outer weight=0.4 controls how much the blended rank affects the# final score; the inner weights are *per-condition* and balance each# other.response = articles.query.near_text( query="transformer architectures", limit=5, boost=Boost.blend( [ Boost.time_decay("published", origin="now", scale=timedelta(days=30), weight=2.0), Boost.numeric_property("likes", modifier=Boost.Modifier.LOG1P, weight=1.0), ], weight=0.4, depth=200, # rescore the top 200 vector matches ), return_properties=["title", "likes", "published"],)Negative weights demote
Section titled “Negative weights demote”A condition with weight: -1.0 (or -2.0, etc.) reverses the effect: documents that match the condition rank below non-matching ones instead of above them. They are not removed. This is useful for deprioritizing (for example, surfacing drafts last without filtering them out).
# A negative per-condition weight pushes matching documents DOWN — they# stay in the result set but lose ground against everything else. Use# this to deprioritize drafts without filtering them out entirely.response = articles.query.bm25( query="transformer", limit=5, boost=Boost.blend( Boost.filter(Filter.by_property("draft").equal(True), weight=-2.0), weight=0.5, ), return_properties=["title", "draft"],)# The draft article is still in results, just no longer first.all_titles = [o.properties["title"] for o in response.objects]assert any("Draft" in t for t in all_titles)assert response.objects[0].properties["draft"] is FalseDepth and pagination
Section titled “Depth and pagination”depth controls the candidate pool. The primary search fetches depth results before the boost rescorer runs. After rescoring, the user's offset and limit are applied.
| Property | Value |
|---|---|
| Default | 100 |
| Operator override | QUERY_BOOST_DEFAULT_DEPTH env var |
| Hard cap | QUERY_MAXIMUM_RESULTS (cluster-wide limit) |
| Lower bound | At least offset + limit, so boost always sees enough to fill the page |
| Accepted range | ≥ 0, where 0 means "use the default" |
Further resources
Section titled “Further resources”- Rerank: second-stage reranking with an external model.
- Hybrid search: the BM25 / vector
alphablend. - Filters: hard filters (remove non-matching docs).
- BM25: keyword search.
Questions and feedback
Section titled “Questions and feedback”Have a question or feedback? Here's how to reach us.