groupBy minDistance / maxDistance do not match the distances of the group's hits
How to reproduce this bug?
Docs describe minDistance / maxDistance as the minimum and maximum distance from the group to the query vector. That should be min / max of the hit distances (Group.min_distance / Group.max_distance in the Python client, or hits[]._additional.distance in GraphQL). Two grouping paths get this wrong.
1. Start Weaviate
docker run --rm -p 8080:8080 -p 50051:50051 \
-e AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true \
-e PERSISTENCE_DATA_PATH=/var/lib/weaviate \
-e CLUSTER_HOSTNAME=node1 \
cr.weaviate.io/semitechnologies/weaviate:1.39.52a. Hybrid groupBy (single shard): both fields are the first hit
Two objects in the same city, different distances to [1, 0, 0]. Request city in return_properties (hybrid grouping reads the property from the search result).
import weaviate
from weaviate.classes.config import Configure, DataType, Property
from weaviate.classes.query import GroupBy, MetadataQuery
client = weaviate.connect_to_local()
try:
if client.collections.exists("GroupByMinMaxHybrid"):
client.collections.delete("GroupByMinMaxHybrid")
col = client.collections.create(
"GroupByMinMaxHybrid",
properties=[
Property(name="title", data_type=DataType.TEXT),
Property(name="city", data_type=DataType.TEXT),
],
vector_config=Configure.Vectors.self_provided(),
)
col.data.insert(
properties={"title": "near", "city": "A"},
vector=[1, 0, 0],
uuid="00000000-0000-0000-0000-000000000001",
)
col.data.insert(
properties={"title": "far", "city": "A"},
vector=[0.7, 0.3, 0],
uuid="00000000-0000-0000-0000-000000000002",
)
def show(label, res):
print(label)
for name, group in res.groups.items():
hits = [
(obj.properties["title"], obj.metadata.distance) for obj in group.objects
]
print(
f" {name!r} count={group.number_of_objects} "
f"min={group.min_distance} max={group.max_distance} hits={hits}"
)
group_by = GroupBy(prop="city", objects_per_group=5, number_of_groups=1)
show(
"hybrid",
col.query.hybrid(
query="near",
vector=[1, 0, 0],
alpha=1,
group_by=group_by,
return_properties=["title", "city"],
return_metadata=MetadataQuery(distance=True),
),
)
show(
"near_vector (1 shard)",
col.query.near_vector(
near_vector=[1, 0, 0],
group_by=group_by,
return_properties=["title", "city"],
return_metadata=MetadataQuery(distance=True),
),
)
finally:
client.close()Same collection with near_vector (no hybrid) on a single shard returns the correct pair (min_distance=0, max_distance≈0.08). Only the hybrid grouping path collapses both fields.
2b. Get nearVector groupBy with 2 shards: the two fields are swapped
import weaviate
from weaviate.classes.config import Configure, DataType, Property
from weaviate.classes.query import GroupBy, MetadataQuery
client = weaviate.connect_to_local()
try:
if client.collections.exists("GroupByMinMaxShards"):
client.collections.delete("GroupByMinMaxShards")
col = client.collections.create(
"GroupByMinMaxShards",
properties=[
Property(name="title", data_type=DataType.TEXT),
Property(name="city", data_type=DataType.TEXT),
],
vector_config=Configure.Vectors.self_provided(),
sharding_config=Configure.sharding(desired_count=2),
)
for i in range(12):
t = i / 11
col.data.insert(
properties={"title": f"o{i}", "city": "A"},
vector=[1.0 - t, t, 0.0],
uuid=f"00000000-0000-0000-0000-0000000001{i:02d}",
)
print("shards", [(s.name, s.status) for s in col.config.get_shards()])
res = col.query.near_vector(
near_vector=[1, 0, 0],
group_by=GroupBy(prop="city", objects_per_group=20, number_of_groups=1),
return_properties=["title", "city"],
return_metadata=MetadataQuery(distance=True),
)
group = res.groups["A"]
dists = [obj.metadata.distance for obj in group.objects]
print(
f"count={group.number_of_objects} min={group.min_distance} "
f"max={group.max_distance} hit_dists={dists}"
)
finally:
client.close()col.config.get_shards() should show two READY shards. The merge path only runs when more than one shard is searched.
What is the expected behavior?
For a group, min_distance is the smallest hit distance and max_distance is the largest.
Hybrid example: hits 0 and ~0.081 → min_distance=0, max_distance≈0.081.
Two-shard example: hits from 0 to 1 → min_distance=0, max_distance=1.
What is the actual behavior?
Hybrid: hits are 0 and 0.08085495, but both group fields are the first hit:
hybrid
'A' count=2 min=0.0 max=0.0 hits=[('near', 0.0), ('far', 0.08085495233535767)]Get near_vector, 1 shard on the same two objects is correct:
near_vector (1 shard)
'A' count=2 min=0.0 max=0.08085495233535767 hits=[('near', 0.0), ('far', 0.08085495233535767)]Get near_vector, 2 shards: hits are sorted 0 … 1, but the fields are reversed (min_distance > max_distance):
shards [('…', 'READY'), ('…', 'READY')]
count=12 min=1.0 max=0.0 hit_dists=[0.0, …, 1.0]Supporting information
Hybrid grouping always copies the first member's distance (usecases/traverser/hybrid_group_by.go):
MinDistance: first.Dist,
MaxDistance: first.Dist,Multi-shard merge sorts hits by distance ascending, then assigns max from hits[0] and min from the last hit (adapters/repos/db/group_merger.go):
sort.Slice(hits, func(i, j int) bool {
return hits[i]["_additional"].(*additional.GroupHitAdditional).Distance <
hits[j]["_additional"].(*additional.GroupHitAdditional).Distance
})
MaxDistance: hits[0]["_additional"].(*additional.GroupHitAdditional).Distance,
MinDistance: hits[len(hits)-1]["_additional"].(*additional.GroupHitAdditional).Distance,Server Version
v1.39.5 (2e3e707)
Weaviate Setup
Single Node
Nodes count
1
Code of Conduct
- I have read and agree to the Weaviate's Contributor Guide and Code of Conduct
Source: weaviate/weaviate