fix: avoid qdrant lexical full scan on empty text hits

This commit is contained in:
Igor Lins e Silva 2026-06-02 21:50:08 -03:00
parent 6aa8e93bc9
commit 30b82ba95f
2 changed files with 23 additions and 1 deletions

View File

@ -1027,18 +1027,20 @@ class QdrantCollection(BaseCollection):
q_filter = None if _requires_local_filter(where) else _qdrant_filter(where)
rows = []
text_filter = _text_any_filter(query)
text_filter_success = False
if text_filter:
try:
rows = self._scroll_all(
qdrant_filter=_combine_filters(q_filter, text_filter),
with_vector=False,
)
text_filter_success = True
except BackendError:
logger.debug(
"Qdrant text filter failed; falling back to lexical scan", exc_info=True
)
rows = []
if not rows:
if not text_filter_success:
rows = self._scroll_all(qdrant_filter=q_filter, with_vector=False)
rows = [row for row in rows if _matches_where(row["metadata"], where)]
scores = _bm25_scores(query, [row["document"] for row in rows])

View File

@ -78,6 +78,7 @@ class _FakeQdrantClient:
def __init__(self, _config):
self.collections = {}
self.query_calls = []
self.scroll_calls = []
self.created_indexes = []
_FakeQdrantClient.instances.append(self)
@ -143,6 +144,7 @@ class _FakeQdrantClient:
offset=None,
with_vector=False,
):
self.scroll_calls.append(qdrant_filter)
points = list(self.collections.get(collection, {"points": {}})["points"].values())
points = [point for point in points if _fake_match_filter(point, qdrant_filter)]
start = int(offset or 0)
@ -312,6 +314,24 @@ def test_qdrant_complex_filters_use_exact_local_fallback(tmp_path, fake_qdrant):
assert fake_client.query_calls == []
def test_qdrant_lexical_empty_text_filter_does_not_full_scan(tmp_path, fake_qdrant):
_backend, col = _collection(tmp_path)
col.upsert(
ids=["a", "b"],
documents=["alpha backend note", "beta frontend note"],
metadatas=[{"wing": "project"}, {"wing": "project"}],
embeddings=[[1, 0], [0, 1]],
)
fake_client = fake_qdrant.instances[0]
fake_client.scroll_calls.clear()
hits = col.lexical_search(query="missingterm", n_results=2).hits
assert hits == []
assert len(fake_client.scroll_calls) == 1
assert "text_any" in str(fake_client.scroll_calls[0])
def test_qdrant_dimension_mismatch(tmp_path, fake_qdrant):
_backend, col = _collection(tmp_path)
col.upsert(ids=["a"], documents=["one"], metadatas=[{}], embeddings=[[1, 0]])