haystack-velesdb 7.0.0__tar.gz → 8.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {haystack_velesdb-7.0.0/src/haystack_velesdb.egg-info → haystack_velesdb-8.0.0}/PKG-INFO +11 -6
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/README.md +8 -3
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/pyproject.toml +3 -3
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb/__init__.py +1 -1
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb/document_store.py +58 -17
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0/src/haystack_velesdb.egg-info}/PKG-INFO +11 -6
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb.egg-info/requires.txt +2 -2
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/tests/test_document_store.py +36 -6
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/LICENSE +0 -0
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/setup.cfg +0 -0
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb/py.typed +0 -0
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb/retriever.py +0 -0
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb.egg-info/SOURCES.txt +0 -0
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb.egg-info/dependency_links.txt +0 -0
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb.egg-info/top_level.txt +0 -0
- {haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/tests/test_real_haystack_integration.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haystack-velesdb
|
|
3
|
-
Version:
|
|
3
|
+
Version: 8.0.0
|
|
4
4
|
Summary: Haystack 2.x DocumentStore for VelesDB: The Local AI Memory Database.
|
|
5
5
|
Author-email: VelesDB Team <contact@wiscale.fr>
|
|
6
6
|
License: MIT
|
|
@@ -20,8 +20,8 @@ Requires-Python: >=3.10
|
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: haystack-ai>=2.0.0
|
|
23
|
-
Requires-Dist: velesdb>=
|
|
24
|
-
Requires-Dist: velesdb-common>=
|
|
23
|
+
Requires-Dist: velesdb>=5.1.0
|
|
24
|
+
Requires-Dist: velesdb-common>=8.0.0
|
|
25
25
|
Provides-Extra: dev
|
|
26
26
|
Requires-Dist: pytest<9.0,>=7.0; extra == "dev"
|
|
27
27
|
Dynamic: license-file
|
|
@@ -160,9 +160,14 @@ integers using the first 8 bytes of SHA-256 (~9.2 × 10¹⁸ slots). For a
|
|
|
160
160
|
is negligible for typical RAG workloads. A `ValueError` is raised at write time
|
|
161
161
|
if a collision is detected between a new document and an existing one.
|
|
162
162
|
|
|
163
|
-
**Note on `scale_score`:** When `True` (default),
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
**Note on `scale_score`:** When `True` (default), scores are higher-is-closer,
|
|
164
|
+
as Haystack's `DocumentJoiner` and rankers expect: cosine similarities are
|
|
165
|
+
normalised from `[-1, 1]` to `[0, 1]`, a `euclidean` or `hamming` distance `d`
|
|
166
|
+
becomes `1 / (1 + d)`, and dot and Jaccard similarities are unchanged. A fused
|
|
167
|
+
score (`fusion=...`) is never rescaled, but one that is still a distance
|
|
168
|
+
(`average`, `maximum` or `weighted` over a distance metric) is mapped to
|
|
169
|
+
`1 / (1 + d)`. The direction of each metric and strategy comes from
|
|
170
|
+
velesdb-core, through `velesdb_common.scores`.
|
|
166
171
|
|
|
167
172
|
**Note on streaming ingestion:** `stream_insert()` and `write_documents_streaming()`
|
|
168
173
|
forward to the underlying `velesdb.Collection.stream_insert`, which requires
|
|
@@ -132,9 +132,14 @@ integers using the first 8 bytes of SHA-256 (~9.2 × 10¹⁸ slots). For a
|
|
|
132
132
|
is negligible for typical RAG workloads. A `ValueError` is raised at write time
|
|
133
133
|
if a collision is detected between a new document and an existing one.
|
|
134
134
|
|
|
135
|
-
**Note on `scale_score`:** When `True` (default),
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
**Note on `scale_score`:** When `True` (default), scores are higher-is-closer,
|
|
136
|
+
as Haystack's `DocumentJoiner` and rankers expect: cosine similarities are
|
|
137
|
+
normalised from `[-1, 1]` to `[0, 1]`, a `euclidean` or `hamming` distance `d`
|
|
138
|
+
becomes `1 / (1 + d)`, and dot and Jaccard similarities are unchanged. A fused
|
|
139
|
+
score (`fusion=...`) is never rescaled, but one that is still a distance
|
|
140
|
+
(`average`, `maximum` or `weighted` over a distance metric) is mapped to
|
|
141
|
+
`1 / (1 + d)`. The direction of each metric and strategy comes from
|
|
142
|
+
velesdb-core, through `velesdb_common.scores`.
|
|
138
143
|
|
|
139
144
|
**Note on streaming ingestion:** `stream_insert()` and `write_documents_streaming()`
|
|
140
145
|
forward to the underlying `velesdb.Collection.stream_insert`, which requires
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "haystack-velesdb"
|
|
7
|
-
version = "
|
|
7
|
+
version = "8.0.0"
|
|
8
8
|
description = "Haystack 2.x DocumentStore for VelesDB: The Local AI Memory Database."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -31,8 +31,8 @@ classifiers = [
|
|
|
31
31
|
requires-python = ">=3.10"
|
|
32
32
|
dependencies = [
|
|
33
33
|
"haystack-ai>=2.0.0",
|
|
34
|
-
"velesdb>=
|
|
35
|
-
"velesdb-common>=
|
|
34
|
+
"velesdb>=5.1.0",
|
|
35
|
+
"velesdb-common>=8.0.0",
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
[project.optional-dependencies]
|
|
@@ -17,6 +17,11 @@ import velesdb
|
|
|
17
17
|
from velesdb_common.collection_admin import CollectionAdminMixin
|
|
18
18
|
from velesdb_common.fusion import build_fusion_strategy
|
|
19
19
|
from velesdb_common.ids import stable_hash_id
|
|
20
|
+
from velesdb_common.scores import (
|
|
21
|
+
fused_higher_is_better,
|
|
22
|
+
score_to_similarity,
|
|
23
|
+
to_similarity,
|
|
24
|
+
)
|
|
20
25
|
from velesdb_common.security import (
|
|
21
26
|
validate_collection_name,
|
|
22
27
|
validate_metric,
|
|
@@ -225,8 +230,38 @@ def _doc_to_point(doc: Document, sparse_vector: Optional[dict] = None) -> dict:
|
|
|
225
230
|
return point
|
|
226
231
|
|
|
227
232
|
|
|
233
|
+
def _doc_score(
|
|
234
|
+
raw: Optional[float],
|
|
235
|
+
*,
|
|
236
|
+
scale_score: bool,
|
|
237
|
+
metric: str,
|
|
238
|
+
higher_is_better: Optional[bool],
|
|
239
|
+
) -> Optional[float]:
|
|
240
|
+
"""The ``Document.score`` of a VelesDB result score.
|
|
241
|
+
|
|
242
|
+
A fused score (``higher_is_better`` given, as core states it for the
|
|
243
|
+
strategy and metric) is always made higher-is-closer: Haystack ranks and
|
|
244
|
+
joins documents by descending score. A plain search score is scaled when
|
|
245
|
+
*scale_score* is set: cosine from ``[-1, 1]`` to ``[0, 1]``, a euclidean
|
|
246
|
+
or hamming distance ``d`` to ``1 / (1 + d)``, dot and Jaccard unchanged.
|
|
247
|
+
"""
|
|
248
|
+
if raw is None:
|
|
249
|
+
return None
|
|
250
|
+
if higher_is_better is not None:
|
|
251
|
+
return to_similarity(raw, higher_is_better)
|
|
252
|
+
if not scale_score:
|
|
253
|
+
return raw
|
|
254
|
+
if metric == "cosine":
|
|
255
|
+
return (raw + 1.0) / 2.0
|
|
256
|
+
return score_to_similarity(metric, raw)
|
|
257
|
+
|
|
258
|
+
|
|
228
259
|
def _result_to_doc(
|
|
229
|
-
result: dict,
|
|
260
|
+
result: dict,
|
|
261
|
+
*,
|
|
262
|
+
scale_score: bool = False,
|
|
263
|
+
metric: str = "cosine",
|
|
264
|
+
higher_is_better: Optional[bool] = None,
|
|
230
265
|
) -> Document:
|
|
231
266
|
"""Convert a VelesDB search or scroll result to a Haystack Document.
|
|
232
267
|
|
|
@@ -256,13 +291,12 @@ def _result_to_doc(
|
|
|
256
291
|
)
|
|
257
292
|
content = payload.get("content")
|
|
258
293
|
meta = {k: v for k, v in payload.items() if k not in _RESERVED_PAYLOAD_KEYS}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
score = raw_score
|
|
294
|
+
score = _doc_score(
|
|
295
|
+
result.get("score"),
|
|
296
|
+
scale_score=scale_score,
|
|
297
|
+
metric=metric,
|
|
298
|
+
higher_is_better=higher_is_better,
|
|
299
|
+
)
|
|
266
300
|
return Document(id=doc_id, content=content, meta=meta, score=score)
|
|
267
301
|
|
|
268
302
|
|
|
@@ -705,11 +739,13 @@ class VelesDBDocumentStore(CollectionAdminMixin):
|
|
|
705
739
|
filters: Optional Haystack 2.x filter dict to restrict the search
|
|
706
740
|
space. Translated to VelesDB native shape before being
|
|
707
741
|
forwarded; ``meta.<key>`` is stripped to ``<key>``.
|
|
708
|
-
scale_score: When ``True
|
|
709
|
-
normalised from ``[-1, 1]`` to ``[0, 1]
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
742
|
+
scale_score: When ``True``, scores are higher-is-closer: cosine
|
|
743
|
+
normalised from ``[-1, 1]`` to ``[0, 1]``, a ``euclidean`` or
|
|
744
|
+
``hamming`` distance ``d`` mapped to ``1 / (1 + d)``, dot and
|
|
745
|
+
Jaccard unchanged. When ``False``, raw scores are returned. A
|
|
746
|
+
fused (``fusion``) score is never rescaled, but a distance it
|
|
747
|
+
keeps (``average``, ``maximum`` or ``weighted`` over a distance
|
|
748
|
+
metric) is mapped to ``1 / (1 + d)`` either way.
|
|
713
749
|
fusion: Optional fusion strategy name applied to the ranking —
|
|
714
750
|
one of ``"average"``, ``"maximum"``, ``"rrf"``,
|
|
715
751
|
``"weighted"``, ``"relative_score"`` / ``"rsf"``. When set,
|
|
@@ -734,7 +770,7 @@ class VelesDBDocumentStore(CollectionAdminMixin):
|
|
|
734
770
|
results: List[dict] = self._get_collection().search_request(
|
|
735
771
|
velesdb.SearchOptions(
|
|
736
772
|
vector=query_embedding,
|
|
737
|
-
|
|
773
|
+
k=top_k,
|
|
738
774
|
filter=veles_filter,
|
|
739
775
|
)
|
|
740
776
|
)
|
|
@@ -766,9 +802,14 @@ class VelesDBDocumentStore(CollectionAdminMixin):
|
|
|
766
802
|
top_k=top_k,
|
|
767
803
|
fusion=strategy,
|
|
768
804
|
)
|
|
769
|
-
# Fused scores are strategy-derived, not metric similarities
|
|
770
|
-
# cosine
|
|
771
|
-
|
|
805
|
+
# Fused scores are strategy-derived, not metric similarities: no
|
|
806
|
+
# cosine rescaling, but a score-space strategy over a distance metric
|
|
807
|
+
# still returns distances, which are made higher-is-closer.
|
|
808
|
+
higher_is_better = fused_higher_is_better(strategy, fusion, self._metric)
|
|
809
|
+
return [
|
|
810
|
+
_result_to_doc(r, metric=self._metric, higher_is_better=higher_is_better)
|
|
811
|
+
for r in results
|
|
812
|
+
]
|
|
772
813
|
|
|
773
814
|
# ------------------------------------------------------------------
|
|
774
815
|
# Haystack pipeline serialisation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haystack-velesdb
|
|
3
|
-
Version:
|
|
3
|
+
Version: 8.0.0
|
|
4
4
|
Summary: Haystack 2.x DocumentStore for VelesDB: The Local AI Memory Database.
|
|
5
5
|
Author-email: VelesDB Team <contact@wiscale.fr>
|
|
6
6
|
License: MIT
|
|
@@ -20,8 +20,8 @@ Requires-Python: >=3.10
|
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: haystack-ai>=2.0.0
|
|
23
|
-
Requires-Dist: velesdb>=
|
|
24
|
-
Requires-Dist: velesdb-common>=
|
|
23
|
+
Requires-Dist: velesdb>=5.1.0
|
|
24
|
+
Requires-Dist: velesdb-common>=8.0.0
|
|
25
25
|
Provides-Extra: dev
|
|
26
26
|
Requires-Dist: pytest<9.0,>=7.0; extra == "dev"
|
|
27
27
|
Dynamic: license-file
|
|
@@ -160,9 +160,14 @@ integers using the first 8 bytes of SHA-256 (~9.2 × 10¹⁸ slots). For a
|
|
|
160
160
|
is negligible for typical RAG workloads. A `ValueError` is raised at write time
|
|
161
161
|
if a collision is detected between a new document and an existing one.
|
|
162
162
|
|
|
163
|
-
**Note on `scale_score`:** When `True` (default),
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
**Note on `scale_score`:** When `True` (default), scores are higher-is-closer,
|
|
164
|
+
as Haystack's `DocumentJoiner` and rankers expect: cosine similarities are
|
|
165
|
+
normalised from `[-1, 1]` to `[0, 1]`, a `euclidean` or `hamming` distance `d`
|
|
166
|
+
becomes `1 / (1 + d)`, and dot and Jaccard similarities are unchanged. A fused
|
|
167
|
+
score (`fusion=...`) is never rescaled, but one that is still a distance
|
|
168
|
+
(`average`, `maximum` or `weighted` over a distance metric) is mapped to
|
|
169
|
+
`1 / (1 + d)`. The direction of each metric and strategy comes from
|
|
170
|
+
velesdb-core, through `velesdb_common.scores`.
|
|
166
171
|
|
|
167
172
|
**Note on streaming ingestion:** `stream_insert()` and `write_documents_streaming()`
|
|
168
173
|
forward to the underlying `velesdb.Collection.stream_insert`, which requires
|
|
@@ -123,14 +123,14 @@ class _FakeSearchOptions:
|
|
|
123
123
|
vector: Any = None,
|
|
124
124
|
*,
|
|
125
125
|
sparse_vector: Any = None,
|
|
126
|
-
|
|
126
|
+
k: int = 10,
|
|
127
127
|
filter: Any = None, # pylint: disable=redefined-builtin
|
|
128
128
|
sparse_index_name: Any = None,
|
|
129
129
|
include_vectors: bool = False,
|
|
130
130
|
) -> None:
|
|
131
131
|
self.vector = vector
|
|
132
132
|
self.sparse_vector = sparse_vector
|
|
133
|
-
self.
|
|
133
|
+
self.k = k
|
|
134
134
|
self.filter = filter
|
|
135
135
|
self.sparse_index_name = sparse_index_name
|
|
136
136
|
self.include_vectors = include_vectors
|
|
@@ -165,7 +165,7 @@ class _FakeCollection:
|
|
|
165
165
|
|
|
166
166
|
def search_request(self, opts: Any) -> list:
|
|
167
167
|
"""Canonical search entry point — delegate to the legacy `search`."""
|
|
168
|
-
return self.search(opts.vector, top_k=opts.
|
|
168
|
+
return self.search(opts.vector, top_k=opts.k, filter=opts.filter)
|
|
169
169
|
|
|
170
170
|
def multi_query_search(
|
|
171
171
|
self,
|
|
@@ -321,6 +321,18 @@ def _load_module() -> types.ModuleType:
|
|
|
321
321
|
sys.modules["velesdb_common.collection_admin"] = vc_admin
|
|
322
322
|
_admin_spec.loader.exec_module(vc_admin)
|
|
323
323
|
|
|
324
|
+
# The REAL velesdb_common.scores too: it imports velesdb lazily-guarded
|
|
325
|
+
# and, against this fake binding, uses its literal fallbacks, so the
|
|
326
|
+
# store's score direction is the canonical one.
|
|
327
|
+
_scores_path = (
|
|
328
|
+
Path(__file__).resolve().parents[2] / "common" / "src" / "velesdb_common" / "scores.py"
|
|
329
|
+
)
|
|
330
|
+
_scores_spec = importlib.util.spec_from_file_location("velesdb_common.scores", _scores_path)
|
|
331
|
+
assert _scores_spec and _scores_spec.loader
|
|
332
|
+
vc_scores = importlib.util.module_from_spec(_scores_spec)
|
|
333
|
+
sys.modules["velesdb_common.scores"] = vc_scores
|
|
334
|
+
_scores_spec.loader.exec_module(vc_scores)
|
|
335
|
+
|
|
324
336
|
vc_sec = types.ModuleType("velesdb_common.security")
|
|
325
337
|
vc_sec.validate_path = _passthrough # type: ignore[attr-defined]
|
|
326
338
|
vc_sec.validate_collection_name = _passthrough # type: ignore[attr-defined]
|
|
@@ -397,12 +409,30 @@ def test_filter_documents_returns_all_when_none() -> None:
|
|
|
397
409
|
assert len(store.filter_documents()) == 2
|
|
398
410
|
|
|
399
411
|
|
|
400
|
-
def
|
|
412
|
+
def test_scale_score_maps_a_distance_to_a_higher_is_closer_score() -> None:
|
|
401
413
|
store = _MOD.VelesDBDocumentStore(path="/tmp/hs", collection_name="t_score_nc", metric="euclidean")
|
|
402
414
|
store.write_documents([Document(id="z", content="raw", embedding=[1.0])])
|
|
403
415
|
scaled = store.embedding_retrieval([1.0], scale_score=True)
|
|
404
|
-
|
|
405
|
-
|
|
416
|
+
raw = store.embedding_retrieval([1.0], scale_score=False)
|
|
417
|
+
# A euclidean score is a distance: DocumentJoiner sorts descending, so the
|
|
418
|
+
# scaled score must grow as the distance shrinks.
|
|
419
|
+
assert scaled[0].score == 1.0 / (1.0 + 0.9)
|
|
420
|
+
assert raw[0].score == 0.9
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def test_scale_score_keeps_a_dot_similarity() -> None:
|
|
424
|
+
store = _MOD.VelesDBDocumentStore(path="/tmp/hs", collection_name="t_score_dot", metric="dot")
|
|
425
|
+
store.write_documents([Document(id="d", content="dot", embedding=[1.0])])
|
|
426
|
+
assert store.embedding_retrieval([1.0], scale_score=True)[0].score == 0.9
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def test_score_space_fusion_over_a_distance_is_higher_is_closer() -> None:
|
|
430
|
+
store = _MOD.VelesDBDocumentStore(path="/tmp/hs", collection_name="t_fused_l2", metric="euclidean")
|
|
431
|
+
store.write_documents([Document(id="f", content="fused", embedding=[1.0])])
|
|
432
|
+
averaged = store.embedding_retrieval([1.0], fusion="average")
|
|
433
|
+
ranked = store.embedding_retrieval([1.0], fusion="rrf")
|
|
434
|
+
assert averaged[0].score == 1.0 / (1.0 + 0.9) # a fused distance, mapped
|
|
435
|
+
assert ranked[0].score == 0.9 # RRF is already higher-is-better
|
|
406
436
|
|
|
407
437
|
|
|
408
438
|
def test_scroll_limit_is_respected() -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{haystack_velesdb-7.0.0 → haystack_velesdb-8.0.0}/src/haystack_velesdb.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|