Chunks preview

This commit is contained in:
Pavel committed 2026-07-13 23:22:17 +03:00
1 parent 73c3dfb5c4
commit 0447eb9b8d
33 files changed
+1961 -109

No files matched your search

+31
View File
@@ -384,3 +384,34 @@ class TestBaseVectorStore:
result = store._get_embeddings("some_custom_embedding")
assert result is mock_emb
mock_get_instance.assert_called_with("some_custom_embedding")
@pytest.mark.unit
class TestSearchWithScoresDefault:
def test_pairs_hits_with_none(self):
"""A store that reports no score still satisfies the contract, so the
retriever never has to special-case it."""
from application.vectorstore.base import BaseVectorStore
class _Store(BaseVectorStore):
def search(self, question, k=2, *args, **kwargs):
return ["a", "b"]
def add_texts(self, texts, metadatas=None, *args, **kwargs):
return []
store = _Store()
assert store.score_kind is None
assert store.search_with_scores("q", k=2) == [("a", None), ("b", None)]
def test_handles_store_returning_none(self):
from application.vectorstore.base import BaseVectorStore
class _Store(BaseVectorStore):
def search(self, question, k=2, *args, **kwargs):
return None
def add_texts(self, texts, metadatas=None, *args, **kwargs):
return []
assert _Store().search_with_scores("q") == []
+64
View File
@@ -562,3 +562,67 @@ class TestFaissStoreAssertEmbeddingDimensionsMatch:
# Should not raise since embedding name is not the huggingface one
store = FaissStore(source_id="t", embeddings_key="k", docs_init=[Mock()])
assert store is not None
@pytest.mark.unit
class TestFaissSearchWithScores:
@staticmethod
def _build(mock_faiss, mock_get_emb, mock_settings, mock_storage_creator, ds):
mock_settings.EMBEDDINGS_NAME = "test_model"
mock_get_emb.return_value = Mock(dimension=3)
mock_faiss.from_documents.return_value = ds
mock_storage_creator.get_storage.return_value = Mock()
from application.vectorstore.faiss import FaissStore
return FaissStore(source_id="t", embeddings_key="k", docs_init=[Mock()])
@patch("application.vectorstore.faiss.StorageCreator")
@patch("application.vectorstore.faiss.FAISS")
@patch.object(
__import__("application.vectorstore.base", fromlist=["BaseVectorStore"]).BaseVectorStore,
"_get_embeddings",
)
@patch("application.vectorstore.faiss.settings")
def test_reports_l2_distance_and_drops_threshold(
self, mock_settings, mock_get_emb, mock_faiss, mock_storage_creator
):
doc = Mock(page_content="text1", metadata={"source": "a"})
ds = Mock(index=Mock(d=3))
ds.similarity_search_with_score.return_value = [(doc, 0.42)]
store = self._build(
mock_faiss, mock_get_emb, mock_settings, mock_storage_creator, ds
)
results = store.search_with_scores("query", k=5, score_threshold=0.9)
# LangChain's FAISS ranks by L2 distance, NOT cosine similarity — the
# label must say so, and score_threshold must be dropped (FAISS has no
# such knob and would crash on it).
assert store.score_kind == "l2_distance"
ds.similarity_search_with_score.assert_called_once_with("query", k=5)
assert results == [(doc, 0.42)]
@patch("application.vectorstore.faiss.StorageCreator")
@patch("application.vectorstore.faiss.FAISS")
@patch.object(
__import__("application.vectorstore.base", fromlist=["BaseVectorStore"]).BaseVectorStore,
"_get_embeddings",
)
@patch("application.vectorstore.faiss.settings")
def test_does_not_mutate_docstore_metadata(
self, mock_settings, mock_get_emb, mock_faiss, mock_storage_creator
):
# similarity_search_with_score hands back the LIVE docstore Documents;
# writing a score into their metadata would pollute the in-memory index
# and could be persisted back to storage by a later add_chunk.
doc = Mock(page_content="text1", metadata={"source": "a"})
ds = Mock(index=Mock(d=3))
ds.similarity_search_with_score.return_value = [(doc, 0.42)]
store = self._build(
mock_faiss, mock_get_emb, mock_settings, mock_storage_creator, ds
)
store.search_with_scores("query", k=1)
assert doc.metadata == {"source": "a"}
+37
View File
@@ -287,3 +287,40 @@ class TestMongoDBVectorStoreDeleteChunk:
result = store.delete_chunk("bad_id")
assert result is False
@pytest.mark.unit
class TestMongoDBSearchWithScores:
def test_reports_vector_search_score(self):
store, mock_collection, _ = _make_mongodb_store()
mock_collection.aggregate.return_value = iter(
[
{
"_id": "id1",
"text": "hello",
"embedding": [0.1],
"source": "a",
"_score": 0.83,
}
]
)
results = store.search_with_scores("query", k=1)
assert store.score_kind == "cosine_similarity"
assert results[0][0].page_content == "hello"
assert results[0][1] == pytest.approx(0.83)
# The score must not leak into metadata — it rides alongside the doc.
assert "_score" not in results[0][0].metadata
def test_score_is_added_even_without_a_threshold(self):
"""The $addFields stage is unconditional, so an unfiltered search still
carries a score (the whole point of the retrieval tester)."""
store, mock_collection, _ = _make_mongodb_store()
mock_collection.aggregate.return_value = iter([])
store.search_with_scores("query", k=1)
pipeline = mock_collection.aggregate.call_args[0][0]
assert any("$addFields" in stage for stage in pipeline)
assert not any("$match" in stage for stage in pipeline)
+35
View File
@@ -403,3 +403,38 @@ class TestPGVectorStoreConnection:
store.__del__()
mock_conn.close.assert_called_once()
@pytest.mark.unit
class TestPGVectorSearchWithScores:
def test_reports_cosine_similarity(self):
store, _, mock_cursor, _ = _make_store()
mock_cursor.fetchall.return_value = [
("close", {"source": "a.txt"}, 0.10),
("far", {"source": "b.txt"}, 0.40),
]
results = store.search_with_scores("query", k=2)
assert store.score_kind == "cosine_similarity"
assert [doc.page_content for doc, _ in results] == ["close", "far"]
# similarity = 1 - cosine distance, the quantity score_threshold uses.
assert results[0][1] == pytest.approx(0.90)
assert results[1][1] == pytest.approx(0.60)
def test_honours_score_threshold(self):
store, _, mock_cursor, _ = _make_store()
mock_cursor.fetchall.return_value = [
("close", {}, 0.10), # sim 0.90 → kept
("far", {}, 0.40), # sim 0.60 → dropped
]
results = store.search_with_scores("query", k=5, score_threshold=0.85)
assert [doc.page_content for doc, _ in results] == ["close"]
def test_returns_empty_on_error(self):
store, _, mock_cursor, _ = _make_store()
mock_cursor.execute.side_effect = Exception("connection lost")
assert store.search_with_scores("query") == []