fix(evidence): fail closed when BM25 is unavailable

This commit is contained in:
2026-08-24 18:21:21 +02:00
parent 29d41ac258
commit 6176410f42
5 changed files with 70 additions and 5 deletions
+18 -2
View File
@@ -102,6 +102,7 @@ class QdrantVectorStore:
write_reachable=False,
write_detail=str(exc),
expected_dimension=self._expected_dimension,
bm25_compatible=None,
)
dimension = info["config"]["params"]["vectors"]["size"]
@@ -118,6 +119,7 @@ class QdrantVectorStore:
expected_dimension=self._expected_dimension,
observed_dimensions=dimensions,
dimension_compatible=compatible,
bm25_compatible=self._bm25_compatible(info),
)
def search(
@@ -176,6 +178,7 @@ class QdrantVectorStore:
raise VectorStoreError("Hybrid BM25 is only available for Evidence")
if query_text.strip() == "" or query_language not in _BM25_LANGUAGES:
raise VectorStoreError("Evidence BM25 query is invalid")
self._ensure_collection(strict=False, require_bm25=True)
shared_filter = {"must": filter_must}
response = self._call(
"POST",
@@ -225,7 +228,10 @@ class QdrantVectorStore:
def upsert(self, collection: str, records: list[VectorWriteRecord]) -> int:
validate_collection(collection)
self._ensure_collection(strict=True)
self._ensure_collection(
strict=True,
require_bm25=any(record.sparse_text is not None for record in records),
)
points = []
for write_record in records:
validate_collection_kinds(collection, [write_record.record.kind])
@@ -377,7 +383,15 @@ class QdrantVectorStore:
else "Embedding dimension does not match configured dimension"
)
def _ensure_collection(self, *, strict: bool) -> dict | None:
@staticmethod
def _bm25_compatible(info: dict) -> bool:
sparse_vectors = info.get("config", {}).get("params", {}).get("sparse_vectors")
if not isinstance(sparse_vectors, dict):
return False
bm25 = sparse_vectors.get("bm25")
return isinstance(bm25, dict) and bm25.get("modifier") == "idf"
def _ensure_collection(self, *, strict: bool, require_bm25: bool = False) -> dict | None:
response = self._call("GET", f"/collections/{self._collection}", None, allow_missing=True)
if response is None:
if not strict:
@@ -420,6 +434,8 @@ class QdrantVectorStore:
f"/collections/{self._collection}/index",
{"field_name": field_name, "field_schema": "keyword"},
)
if require_bm25 and not self._bm25_compatible(result):
raise VectorStoreError("Evidence BM25 collection configuration mismatch")
return result
def _scroll(self, must: list[dict]) -> list[dict]: