Skip to content

Commit ec1073b

Browse files
committed
Review feedback, property -> numeric_property and other changes
1 parent 9e311e1 commit ec1073b

3 files changed

Lines changed: 71 additions & 56 deletions

File tree

integration/test_collection_boost.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def test_boost_property_value(collection_factory: CollectionFactory) -> None:
153153
result = collection.query.near_vector(
154154
near_vector=[1.0, 0.0, 0.0],
155155
limit=5,
156-
boost=Boost.property(
156+
boost=Boost.numeric_property(
157157
"count",
158158
modifier=Boost.Modifier.LOG1P,
159159
weight=1.0,
@@ -172,16 +172,18 @@ def test_boost_blend(collection_factory: CollectionFactory) -> None:
172172
near_vector=[1.0, 0.0, 0.0],
173173
limit=5,
174174
boost=Boost.blend(
175-
Boost.filter(
176-
Filter.by_property("rating").greater_or_equal(4.0),
177-
weight=2.0,
178-
),
179-
Boost.numeric_decay(
180-
"price",
181-
origin=30.0,
182-
scale=100.0,
183-
curve=Boost.Curve.EXPONENTIAL,
184-
),
175+
[
176+
Boost.filter(
177+
Filter.by_property("rating").greater_or_equal(4.0),
178+
weight=2.0,
179+
),
180+
Boost.numeric_decay(
181+
"price",
182+
origin=30.0,
183+
scale=100.0,
184+
curve=Boost.Curve.EXPONENTIAL,
185+
),
186+
],
185187
weight=0.8,
186188
),
187189
return_metadata=MetadataQuery(distance=True),
@@ -257,21 +259,28 @@ def test_boost_api_surface() -> None:
257259
assert b.weight == 0.5
258260

259261
b = Boost.blend(
260-
Boost.filter(Filter.by_property("x").equal("y"), weight=1.0),
261-
Boost.property("z", modifier=Boost.Modifier.LOG1P),
262+
[
263+
Boost.filter(Filter.by_property("x").equal("y"), weight=1.0),
264+
Boost.numeric_property("z", modifier=Boost.Modifier.LOG1P),
265+
],
262266
weight=0.8,
263267
depth=200,
264268
)
265269
assert len(b.conditions) == 2
266270
assert b.weight == 0.8
267271
assert b.depth == 200
268272

273+
# blend() also accepts a single boost
274+
b = Boost.blend(Boost.filter(Filter.by_property("x").equal("y")), weight=0.5)
275+
assert len(b.conditions) == 1
276+
assert b.weight == 0.5
277+
269278

270279
def test_boost_blend_rejects_sub_boost_depth() -> None:
271280
"""blend() raises if any sub-boost has depth set."""
272281
with pytest.raises(WeaviateInvalidInputError):
273282
Boost.blend(
274-
Boost.property("count", depth=500),
283+
Boost.numeric_property("count", depth=500),
275284
depth=100,
276285
)
277286

@@ -287,5 +296,5 @@ def test_boost_default_curve_is_unspecified() -> None:
287296

288297
def test_boost_default_modifier_is_unspecified() -> None:
289298
"""Omitting modifier defaults to None (sent as UNSPECIFIED on the wire)."""
290-
b = Boost.property("count")
299+
b = Boost.numeric_property("count")
291300
assert b.conditions[0].property_value.modifier is None

weaviate/collections/classes/grpc.py

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,8 @@ class _Boost:
315315
BoostReturn: TypeAlias = _Boost
316316

317317

318-
def _decay_value_to_str(val: Union[str, timedelta, datetime]) -> str:
319-
"""Convert a decay parameter value to the string format expected by the server."""
318+
def _decay_duration_to_str(val: Union[str, timedelta]) -> str:
319+
"""Convert a decay duration (scale/offset) to the duration string format expected by the server, e.g. "7d"."""
320320
if isinstance(val, timedelta):
321321
total_seconds = val.total_seconds()
322322
if total_seconds >= 86400 and total_seconds % 86400 == 0:
@@ -328,17 +328,21 @@ def _decay_value_to_str(val: Union[str, timedelta, datetime]) -> str:
328328
if total_seconds == int(total_seconds):
329329
return f"{int(total_seconds)}s"
330330
return f"{total_seconds}s"
331+
return val
332+
333+
334+
def _decay_origin_to_str(val: Union[str, datetime]) -> str:
335+
"""Convert a decay origin to the RFC3339 string format expected by the server, or pass through "now"."""
331336
if isinstance(val, datetime):
332337
if val.tzinfo is None:
333338
val = val.replace(tzinfo=timezone.utc)
334339
return val.isoformat()
335-
return str(val)
340+
return val
336341

337342

338343
class _BoostCurve(str, BaseEnum):
339344
"""Decay curve type for distance-based boost scoring."""
340345

341-
UNSPECIFIED = "unspecified"
342346
EXPONENTIAL = "exp"
343347
GAUSSIAN = "gauss"
344348
LINEAR = "linear"
@@ -347,15 +351,14 @@ class _BoostCurve(str, BaseEnum):
347351
class _BoostModifier(str, BaseEnum):
348352
"""Score modifier for property-value boost scoring."""
349353

350-
UNSPECIFIED = "unspecified"
351354
LOG1P = "log1p"
352355
SQRT = "sqrt"
353356

354357

355358
class Boost:
356359
"""Define soft-scoring conditions to boost or demote matching documents without excluding them.
357360
358-
Use the static methods `filter()`, `time_decay()`, `numeric_decay()`, `property()`, and `blend()` to create boost configurations.
361+
Use the static methods `filter()`, `time_decay()`, `numeric_decay()`, `numeric_property()`, and `blend()` to create boost configurations.
359362
"""
360363

361364
Curve = _BoostCurve
@@ -370,13 +373,13 @@ def filter( # noqa: A003
370373
*,
371374
weight: Optional[float] = None,
372375
depth: Optional[int] = None,
373-
) -> _Boost:
376+
) -> BoostReturn:
374377
"""Boost or demote results matching a filter condition.
375378
376379
Args:
377380
filter: The filter condition (same as used in `filters=` parameter).
378-
weight: Weight controlling how much the boost affects final scores.
379-
depth: Number of results to rescore (default 100, max 10000). Higher values improve accuracy at the cost of performance.
381+
weight: Weight controlling how much the boost affects final scores. Uses the server-side default if not set.
382+
depth: Number of results to rescore. Uses the server-side default if not set. Higher values improve accuracy at the cost of performance.
380383
"""
381384
return _Boost(conditions=[_BoostCondition(filter=filter)], weight=weight, depth=depth)
382385

@@ -391,7 +394,7 @@ def time_decay(
391394
decay: Optional[float] = None,
392395
weight: Optional[float] = None,
393396
depth: Optional[int] = None,
394-
) -> _Boost:
397+
) -> BoostReturn:
395398
"""Apply time-based decay scoring from an origin date.
396399
397400
Args:
@@ -400,21 +403,22 @@ def time_decay(
400403
Defaults to "now".
401404
scale: Distance from origin where score equals decay. Use timedelta
402405
(e.g. timedelta(days=7)) or a string shorthand like "7d", "24h".
403-
offset: Documents within this distance from origin get full score (default "0").
404-
Accepts the same types as scale.
405-
curve: Decay curve type: `Boost.Curve.EXPONENTIAL` (default), `Boost.Curve.GAUSSIAN`, or `Boost.Curve.LINEAR`.
406-
decay: Score at scale distance from origin (default 0.5).
407-
weight: Weight controlling how much the boost affects final scores.
408-
depth: Number of results to rescore (default 100, max 10000).
406+
offset: Documents within this distance from origin get full score.
407+
Accepts the same types as scale. Uses the server-side default if not set.
408+
curve: Decay curve type: `Boost.Curve.EXPONENTIAL`, `Boost.Curve.GAUSSIAN`, or `Boost.Curve.LINEAR`.
409+
Uses the server-side default if not set.
410+
decay: Score at scale distance from origin. Uses the server-side default if not set.
411+
weight: Weight controlling how much the boost affects final scores. Uses the server-side default if not set.
412+
depth: Number of results to rescore. Uses the server-side default if not set.
409413
"""
410414
return _Boost(
411415
conditions=[
412416
_BoostCondition(
413417
time_decay=_TimeDecayFunction(
414418
property=property,
415-
origin=_decay_value_to_str(origin) if origin is not None else "now",
416-
scale=_decay_value_to_str(scale),
417-
offset=_decay_value_to_str(offset) if offset is not None else None,
419+
origin=_decay_origin_to_str(origin) if origin is not None else "now",
420+
scale=_decay_duration_to_str(scale),
421+
offset=_decay_duration_to_str(offset) if offset is not None else None,
418422
curve=curve,
419423
decay_value=decay,
420424
)
@@ -435,22 +439,23 @@ def numeric_decay(
435439
decay: Optional[float] = None,
436440
weight: Optional[float] = None,
437441
depth: Optional[int] = None,
438-
) -> _Boost:
442+
) -> BoostReturn:
439443
"""Score decays with distance from a numeric origin — closer to the origin ranks higher.
440444
441-
Use this when "closer to X is better" (e.g., prefer prices near $50, houses near 2000 sqft).
445+
Use this when "closer to X is better" (e.g., prefer prices near $50, apartments near 80 m²).
442446
Requires you to define an origin and scale. For simple "higher is better" boosting without
443-
an origin, use `Boost.property()` instead.
447+
an origin, use `Boost.numeric_property()` instead.
444448
445449
Args:
446450
property: The numeric property name to compute distance from.
447451
origin: The target value — documents closest to this score highest.
448452
scale: Distance from origin where score equals the decay value.
449-
offset: Documents within this distance from origin get full score (default 0).
450-
curve: Decay curve type: `Boost.Curve.EXPONENTIAL` (default), `Boost.Curve.GAUSSIAN`, or `Boost.Curve.LINEAR`.
451-
decay: Score at scale distance from origin (default 0.5).
452-
weight: Weight controlling how much the boost affects final scores.
453-
depth: Number of results to rescore (default 100, max 10000).
453+
offset: Documents within this distance from origin get full score. Uses the server-side default if not set.
454+
curve: Decay curve type: `Boost.Curve.EXPONENTIAL`, `Boost.Curve.GAUSSIAN`, or `Boost.Curve.LINEAR`.
455+
Uses the server-side default if not set.
456+
decay: Score at scale distance from origin. Uses the server-side default if not set.
457+
weight: Weight controlling how much the boost affects final scores. Uses the server-side default if not set.
458+
depth: Number of results to rescore. Uses the server-side default if not set.
454459
"""
455460
return _Boost(
456461
conditions=[
@@ -470,27 +475,27 @@ def numeric_decay(
470475
)
471476

472477
@staticmethod
473-
def property( # noqa: A003
478+
def numeric_property(
474479
name: str,
475480
*,
476481
modifier: Optional[_BoostModifier] = None,
477482
weight: Optional[float] = None,
478483
depth: Optional[int] = None,
479-
) -> _Boost:
484+
) -> BoostReturn:
480485
"""Boost by a numeric property's raw value — higher values rank higher.
481486
482487
Use this for simple proportional boosting (e.g., popularity count, review score)
483488
when you don't need to define an origin or scale. For distance-based decay from a
484489
specific value, use `Boost.numeric_decay()` instead.
485490
486-
Currently only supports numeric (int/float) properties.
491+
Only supports numeric (int/float) properties. To boost by other property types, use `Boost.filter()`.
487492
488493
Args:
489494
name: The numeric property name to use as a ranking signal.
490-
modifier: Score modifier: `Boost.Modifier.NONE` (default), `Boost.Modifier.LOG1P`, or `Boost.Modifier.SQRT`.
491-
Use LOG1P or SQRT to dampen the effect of large value ranges.
492-
weight: Weight controlling how much the boost affects final scores.
493-
depth: Number of results to rescore (default 100, max 10000).
495+
modifier: Score modifier: `Boost.Modifier.LOG1P` or `Boost.Modifier.SQRT` to dampen
496+
the effect of large value ranges. If not set, the raw value is used.
497+
weight: Weight controlling how much the boost affects final scores. Uses the server-side default if not set.
498+
depth: Number of results to rescore. Uses the server-side default if not set.
494499
"""
495500
return _Boost(
496501
conditions=[
@@ -507,23 +512,26 @@ def property( # noqa: A003
507512

508513
@staticmethod
509514
def blend(
510-
*boosts: _Boost,
515+
boosts: Union[BoostReturn, Sequence[BoostReturn]],
516+
*,
511517
weight: Optional[float] = None,
512518
depth: Optional[int] = None,
513-
) -> _Boost:
519+
) -> BoostReturn:
514520
"""Combine multiple boost conditions with individual weights.
515521
516522
When blending, each sub-boost's weight becomes a per-condition weight,
517523
and the `weight` parameter here controls the overall blending strength.
518524
519525
Args:
520-
*boosts: Boost objects created via `Boost.filter()`, `Boost.time_decay()`, `Boost.numeric_decay()`, or `Boost.property()`.
521-
weight: Overall blending weight [0,1] for combining primary search and boost scores.
522-
depth: Number of results to rescore (default 100, max 10000). Higher values improve accuracy at the cost of performance.
526+
boosts: One or more Boost objects created via `Boost.filter()`, `Boost.time_decay()`, `Boost.numeric_decay()`, or `Boost.numeric_property()`.
527+
weight: Overall blending weight [0,1] for combining primary search and boost scores. Uses the server-side default if not set.
528+
depth: Number of results to rescore. Uses the server-side default if not set. Higher values improve accuracy at the cost of performance.
523529
524530
Raises:
525531
WeaviateInvalidInputError: If no boosts are provided or if any sub-boost has `depth` set.
526532
"""
533+
if isinstance(boosts, _Boost):
534+
boosts = [boosts]
527535
if len(boosts) == 0:
528536
raise WeaviateInvalidInputError("Boost.blend() requires at least one boost.")
529537
for r in boosts:

weaviate/collections/grpc/query.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -563,14 +563,12 @@ def _metadata_to_grpc(self, metadata: _MetadataQuery) -> search_get_pb2.Metadata
563563
_Boost_pb2 = search_get_pb2.Boost
564564

565565
_CURVE_TO_PROTO = {
566-
_BoostCurve.UNSPECIFIED: _Boost_pb2.DECAY_CURVE_UNSPECIFIED,
567566
_BoostCurve.EXPONENTIAL: _Boost_pb2.DECAY_CURVE_EXPONENTIAL,
568567
_BoostCurve.GAUSSIAN: _Boost_pb2.DECAY_CURVE_GAUSS,
569568
_BoostCurve.LINEAR: _Boost_pb2.DECAY_CURVE_LINEAR,
570569
}
571570

572571
_MODIFIER_TO_PROTO = {
573-
_BoostModifier.UNSPECIFIED: _Boost_pb2.PROPERTY_VALUE_MODIFIER_UNSPECIFIED,
574572
_BoostModifier.LOG1P: _Boost_pb2.PROPERTY_VALUE_MODIFIER_LOG1P,
575573
_BoostModifier.SQRT: _Boost_pb2.PROPERTY_VALUE_MODIFIER_SQRT,
576574
}

0 commit comments

Comments
 (0)