#13108·weaviate

groupBy minDistance / maxDistance do not match the distances of the group's hits

Author: woaiqjjCreated Sep 16, 2026Updated Sep 16, 2026
Labelsbugcommunity

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

bash
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.5

2a. 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).

python
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

python
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.081min_distance=0, max_distance≈0.081.

Two-shard example: hits from 0 to 1min_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):

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):

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