Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/qql/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ def _execute_search(self, node: SearchStmt) -> ExecutionResult:
collection_name=node.collection,
prefetch=[
Prefetch(
query=dense_vector,
query=self._build_dense_query(dense_vector, node.with_clause),
using=topology.dense_using(node.dense_vector),
limit=node.limit * _HYBRID_PREFETCH_MULTIPLIER,
params=search_params,
Expand Down Expand Up @@ -1460,8 +1460,6 @@ def _has_mmr(self, with_clause: SearchWith | None) -> bool:
def _validate_search_mmr_usage(self, node: SearchStmt) -> None:
if not self._has_mmr(node.with_clause):
return
if node.hybrid:
raise QQLRuntimeError("MMR is not supported with USING HYBRID yet")
if node.sparse_only:
raise QQLRuntimeError("MMR is not supported with USING SPARSE yet")

Expand Down Expand Up @@ -1635,7 +1633,7 @@ def _execute_search_groups(
group_by=node.group_by,
prefetch=[
Prefetch(
query=dense_vector,
query=self._build_dense_query(dense_vector, node.with_clause),
using=topology.dense_using(node.dense_vector),
limit=node.limit * _HYBRID_PREFETCH_MULTIPLIER,
params=search_params,
Expand Down
62 changes: 58 additions & 4 deletions tests/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1242,18 +1242,72 @@ def test_dense_search_with_mmr_uses_nearest_query(self, executor, mock_client, m
assert query.mmr.diversity == pytest.approx(0.4)
assert query.mmr.candidates_limit == 25

def test_hybrid_search_with_mmr_raises(self, executor, mock_client):
def test_hybrid_search_with_mmr_uses_nearest_query_in_prefetch(self, executor, mock_client, mocker):
from qdrant_client.models import NearestQuery

mocker.patch("qql.executor.Embedder", return_value=mocker.MagicMock())
mocker.patch("qql.executor.SparseEmbedder", return_value=mocker.MagicMock())
mock_client.collection_exists.return_value = True

collection_info = mocker.MagicMock()
collection_info.config.params.vectors = {"dense": {}}
collection_info.config.params.sparse_vectors = {"sparse": {}}
mock_client.get_collection.return_value = collection_info

mock_response = mocker.MagicMock()
mock_response.points = []
mock_client.query_points.return_value = mock_response

node = SearchStmt(
collection="notes",
query_text="hello",
limit=5,
model=None,
hybrid=True,
with_clause=SearchWith(mmr_diversity=0.5),
with_clause=SearchWith(mmr_diversity=0.5, mmr_candidates=30),
)
with pytest.raises(QQLRuntimeError, match="MMR is not supported with USING HYBRID yet"):
executor.execute(node)
executor.execute(node)

prefetch = mock_client.query_points.call_args.kwargs["prefetch"]
assert prefetch is not None
dense_prefetch = next(p for p in prefetch if p.using == "dense")
dense_query = dense_prefetch.query
assert isinstance(dense_query, NearestQuery)
assert dense_query.mmr is not None
assert dense_query.mmr.diversity == pytest.approx(0.5)
assert dense_query.mmr.candidates_limit == 30

def test_hybrid_search_with_mmr_grouped_uses_nearest_query_in_prefetch(self, executor, mock_client, mocker):
from qdrant_client.models import NearestQuery

_mock_hybrid_collection(mock_client)
mock_response = mocker.MagicMock()
mock_response.groups = []
mock_client.query_points_groups.return_value = mock_response

mock_sparse_embedder = mocker.MagicMock()
mock_sparse_embedder.query_embed.return_value = {"indices": [0, 1], "values": [0.5, 0.5]}
mocker.patch("qql.executor.SparseEmbedder", return_value=mock_sparse_embedder)

node = SearchStmt(
collection="articles",
query_text="hello",
limit=5,
model=None,
hybrid=True,
group_by="category",
with_clause=SearchWith(mmr_diversity=0.5, mmr_candidates=30),
)
executor.execute(node)

prefetch = mock_client.query_points_groups.call_args.kwargs["prefetch"]
assert prefetch is not None
dense_prefetch = next(p for p in prefetch if p.using == "dense")
dense_query = dense_prefetch.query
assert isinstance(dense_query, NearestQuery)
assert dense_query.mmr is not None
assert dense_query.mmr.diversity == pytest.approx(0.5)
assert dense_query.mmr.candidates_limit == 30

def test_sparse_search_with_mmr_raises(self, executor, mock_client):
mock_client.collection_exists.return_value = True
Expand Down
Loading