fastembed-haystack 0.0.6__tar.gz → 0.1.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.
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/PKG-INFO +29 -4
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/README.md +25 -0
- {fastembed_haystack-0.0.6/example → fastembed_haystack-0.1.0/examples}/example.py +1 -1
- fastembed_haystack-0.1.0/examples/sparse_example.py +32 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/pydoc/config.yml +3 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/pyproject.toml +5 -5
- fastembed_haystack-0.1.0/src/haystack_integrations/components/embedders/fastembed/__init__.py +14 -0
- fastembed_haystack-0.1.0/src/haystack_integrations/components/embedders/fastembed/embedding_backend/fastembed_backend.py +109 -0
- fastembed_haystack-0.1.0/src/haystack_integrations/components/embedders/fastembed/fastembed_sparse_document_embedder.py +160 -0
- fastembed_haystack-0.1.0/src/haystack_integrations/components/embedders/fastembed/fastembed_sparse_text_embedder.py +117 -0
- fastembed_haystack-0.1.0/tests/test_fastembed_sparse_document_embedder.py +283 -0
- fastembed_haystack-0.1.0/tests/test_fastembed_sparse_text_embedder.py +220 -0
- fastembed_haystack-0.0.6/src/haystack_integrations/components/embedders/fastembed/__init__.py +0 -7
- fastembed_haystack-0.0.6/src/haystack_integrations/components/embedders/fastembed/embedding_backend/fastembed_backend.py +0 -52
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/.gitignore +0 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/LICENSE.txt +0 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/src/haystack_integrations/components/embedders/fastembed/embedding_backend/__init__.py +0 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/src/haystack_integrations/components/embedders/fastembed/fastembed_document_embedder.py +0 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/src/haystack_integrations/components/embedders/fastembed/fastembed_text_embedder.py +0 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/tests/__init__.py +0 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/tests/test_fastembed_backend.py +0 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/tests/test_fastembed_document_embedder.py +0 -0
- {fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/tests/test_fastembed_text_embedder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: fastembed-haystack
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Haystack 2.x component to embed strings and Documents using fastembed embedding model
|
|
5
5
|
Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations
|
|
6
6
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/fastembed/README.md
|
|
@@ -18,8 +18,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
19
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
20
20
|
Requires-Python: >=3.8
|
|
21
|
-
Requires-Dist: fastembed>=0.2
|
|
22
|
-
Requires-Dist: haystack-ai
|
|
21
|
+
Requires-Dist: fastembed>=0.2.5
|
|
22
|
+
Requires-Dist: haystack-ai>=2.0.1
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
|
|
25
25
|
# fastembed-haystack
|
|
@@ -67,6 +67,31 @@ doc = Document(content="fastembed is supported by and maintained by Qdrant.", me
|
|
|
67
67
|
result = embedder.run(documents=[doc])
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
You can use `FastembedSparseTextEmbedder` and `FastembedSparseDocumentEmbedder` by importing as:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from haystack_integrations.components.embedders.fastembed import FastembedSparseTextEmbedder
|
|
74
|
+
|
|
75
|
+
text = "fastembed is supported by and maintained by Qdrant."
|
|
76
|
+
text_embedder = FastembedSparseTextEmbedder(
|
|
77
|
+
model="prithvida/Splade_PP_en_v1"
|
|
78
|
+
)
|
|
79
|
+
text_embedder.warm_up()
|
|
80
|
+
embedding = text_embedder.run(text)["embedding"]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from haystack_integrations.components.embedders.fastembed import FastembedSparseDocumentEmbedder
|
|
85
|
+
from haystack.dataclasses import Document
|
|
86
|
+
|
|
87
|
+
embedder = FastembedSparseDocumentEmbedder(
|
|
88
|
+
model="prithvida/Splade_PP_en_v1",
|
|
89
|
+
)
|
|
90
|
+
embedder.warm_up()
|
|
91
|
+
doc = Document(content="fastembed is supported by and maintained by Qdrant.", meta={"long_answer": "no",})
|
|
92
|
+
result = embedder.run(documents=[doc])
|
|
93
|
+
```
|
|
94
|
+
|
|
70
95
|
## License
|
|
71
96
|
|
|
72
97
|
`fastembed-haystack` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
|
|
@@ -43,6 +43,31 @@ doc = Document(content="fastembed is supported by and maintained by Qdrant.", me
|
|
|
43
43
|
result = embedder.run(documents=[doc])
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
You can use `FastembedSparseTextEmbedder` and `FastembedSparseDocumentEmbedder` by importing as:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from haystack_integrations.components.embedders.fastembed import FastembedSparseTextEmbedder
|
|
50
|
+
|
|
51
|
+
text = "fastembed is supported by and maintained by Qdrant."
|
|
52
|
+
text_embedder = FastembedSparseTextEmbedder(
|
|
53
|
+
model="prithvida/Splade_PP_en_v1"
|
|
54
|
+
)
|
|
55
|
+
text_embedder.warm_up()
|
|
56
|
+
embedding = text_embedder.run(text)["embedding"]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from haystack_integrations.components.embedders.fastembed import FastembedSparseDocumentEmbedder
|
|
61
|
+
from haystack.dataclasses import Document
|
|
62
|
+
|
|
63
|
+
embedder = FastembedSparseDocumentEmbedder(
|
|
64
|
+
model="prithvida/Splade_PP_en_v1",
|
|
65
|
+
)
|
|
66
|
+
embedder.warm_up()
|
|
67
|
+
doc = Document(content="fastembed is supported by and maintained by Qdrant.", meta={"long_answer": "no",})
|
|
68
|
+
result = embedder.run(documents=[doc])
|
|
69
|
+
```
|
|
70
|
+
|
|
46
71
|
## License
|
|
47
72
|
|
|
48
73
|
`fastembed-haystack` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
|
|
@@ -26,7 +26,7 @@ query = "Who supports fastembed?"
|
|
|
26
26
|
|
|
27
27
|
result = query_pipeline.run({"text_embedder": {"text": query}})
|
|
28
28
|
|
|
29
|
-
print(result["retriever"]["documents"][0])
|
|
29
|
+
print(result["retriever"]["documents"][0])
|
|
30
30
|
|
|
31
31
|
# Document(id=...,
|
|
32
32
|
# content: 'fastembed is supported by and maintained by Qdrant.',
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Currently, this example shows how to use the FastembedSparseDocumentEmbedder component to embed a list of documents.
|
|
2
|
+
|
|
3
|
+
# TODO: Once we have a proper SparseEmbeddingRetriever, we should replace this naive example with a more realistic one,
|
|
4
|
+
# involving indexing and retrieval of documents.
|
|
5
|
+
|
|
6
|
+
from haystack import Document
|
|
7
|
+
from haystack_integrations.components.embedders.fastembed import FastembedSparseDocumentEmbedder
|
|
8
|
+
|
|
9
|
+
document_list = [
|
|
10
|
+
Document(
|
|
11
|
+
content="Oxidative stress generated within inflammatory joints can produce autoimmune phenomena and joint destruction. Radical species with oxidative activity, including reactive nitrogen species, represent mediators of inflammation and cartilage damage.",
|
|
12
|
+
meta={
|
|
13
|
+
"pubid": "25,445,628",
|
|
14
|
+
"long_answer": "yes",
|
|
15
|
+
},
|
|
16
|
+
),
|
|
17
|
+
Document(
|
|
18
|
+
content="Plasma levels of pancreatic polypeptide (PP) rise upon food intake. Although other pancreatic islet hormones, such as insulin and glucagon, have been extensively investigated, PP secretion and actions are still poorly understood.",
|
|
19
|
+
meta={
|
|
20
|
+
"pubid": "25,445,712",
|
|
21
|
+
"long_answer": "yes",
|
|
22
|
+
},
|
|
23
|
+
),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
document_embedder = FastembedSparseDocumentEmbedder()
|
|
27
|
+
document_embedder.warm_up()
|
|
28
|
+
documents_with_embeddings = document_embedder.run(document_list)["documents"]
|
|
29
|
+
|
|
30
|
+
for doc in documents_with_embeddings:
|
|
31
|
+
print(f"Document Text: {doc.content}")
|
|
32
|
+
print(f"Document Sparse Embedding: {doc.sparse_embedding.to_dict()}")
|
|
@@ -5,6 +5,8 @@ loaders:
|
|
|
5
5
|
[
|
|
6
6
|
"haystack_integrations.components.embedders.fastembed.fastembed_document_embedder",
|
|
7
7
|
"haystack_integrations.components.embedders.fastembed.fastembed_text_embedder",
|
|
8
|
+
"haystack_integrations.components.embedders.fastembed.fastembed_sparse_document_embedder",
|
|
9
|
+
"haystack_integrations.components.embedders.fastembed.fastembed_sparse_text_embedder"
|
|
8
10
|
]
|
|
9
11
|
ignore_when_discovered: ["__init__"]
|
|
10
12
|
processors:
|
|
@@ -24,6 +26,7 @@ renderer:
|
|
|
24
26
|
order: 80
|
|
25
27
|
markdown:
|
|
26
28
|
descriptive_class_title: false
|
|
29
|
+
classdef_code_block: false
|
|
27
30
|
descriptive_module_title: true
|
|
28
31
|
add_method_class_prefix: true
|
|
29
32
|
add_member_class_prefix: false
|
|
@@ -25,8 +25,8 @@ classifiers = [
|
|
|
25
25
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
26
26
|
]
|
|
27
27
|
dependencies = [
|
|
28
|
-
"haystack-ai",
|
|
29
|
-
"fastembed>=0.2",
|
|
28
|
+
"haystack-ai>=2.0.1",
|
|
29
|
+
"fastembed>=0.2.5",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
[project.urls]
|
|
@@ -95,12 +95,12 @@ all = [
|
|
|
95
95
|
]
|
|
96
96
|
|
|
97
97
|
[tool.black]
|
|
98
|
-
target-version = ["
|
|
98
|
+
target-version = ["py38"]
|
|
99
99
|
line-length = 120
|
|
100
100
|
skip-string-normalization = true
|
|
101
101
|
|
|
102
102
|
[tool.ruff]
|
|
103
|
-
target-version = "
|
|
103
|
+
target-version = "py38"
|
|
104
104
|
line-length = 120
|
|
105
105
|
select = [
|
|
106
106
|
"A",
|
|
@@ -155,7 +155,7 @@ ban-relative-imports = "parents"
|
|
|
155
155
|
# Tests can use magic values, assertions, and relative imports
|
|
156
156
|
"tests/**/*" = ["PLR2004", "S101", "TID252"]
|
|
157
157
|
# examples can contain "print" commands
|
|
158
|
-
"examples/**/*" = ["T201"]
|
|
158
|
+
"examples/**/*" = ["T201", "E501"]
|
|
159
159
|
|
|
160
160
|
[tool.coverage.run]
|
|
161
161
|
source = ["haystack_integrations"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2024-present deepset GmbH <info@deepset.ai>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
from .fastembed_document_embedder import FastembedDocumentEmbedder
|
|
5
|
+
from .fastembed_sparse_document_embedder import FastembedSparseDocumentEmbedder
|
|
6
|
+
from .fastembed_sparse_text_embedder import FastembedSparseTextEmbedder
|
|
7
|
+
from .fastembed_text_embedder import FastembedTextEmbedder
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"FastembedDocumentEmbedder",
|
|
11
|
+
"FastembedTextEmbedder",
|
|
12
|
+
"FastembedSparseDocumentEmbedder",
|
|
13
|
+
"FastembedSparseTextEmbedder",
|
|
14
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from typing import ClassVar, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from haystack.dataclasses.sparse_embedding import SparseEmbedding
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
|
|
6
|
+
from fastembed import TextEmbedding
|
|
7
|
+
from fastembed.sparse.sparse_text_embedding import SparseTextEmbedding
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _FastembedEmbeddingBackendFactory:
|
|
11
|
+
"""
|
|
12
|
+
Factory class to create instances of fastembed embedding backends.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_instances: ClassVar[Dict[str, "_FastembedEmbeddingBackend"]] = {}
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def get_embedding_backend(
|
|
19
|
+
model_name: str,
|
|
20
|
+
cache_dir: Optional[str] = None,
|
|
21
|
+
threads: Optional[int] = None,
|
|
22
|
+
):
|
|
23
|
+
embedding_backend_id = f"{model_name}{cache_dir}{threads}"
|
|
24
|
+
|
|
25
|
+
if embedding_backend_id in _FastembedEmbeddingBackendFactory._instances:
|
|
26
|
+
return _FastembedEmbeddingBackendFactory._instances[embedding_backend_id]
|
|
27
|
+
|
|
28
|
+
embedding_backend = _FastembedEmbeddingBackend(model_name=model_name, cache_dir=cache_dir, threads=threads)
|
|
29
|
+
_FastembedEmbeddingBackendFactory._instances[embedding_backend_id] = embedding_backend
|
|
30
|
+
return embedding_backend
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _FastembedEmbeddingBackend:
|
|
34
|
+
"""
|
|
35
|
+
Class to manage fastembed embeddings.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
model_name: str,
|
|
41
|
+
cache_dir: Optional[str] = None,
|
|
42
|
+
threads: Optional[int] = None,
|
|
43
|
+
):
|
|
44
|
+
self.model = TextEmbedding(model_name=model_name, cache_dir=cache_dir, threads=threads)
|
|
45
|
+
|
|
46
|
+
def embed(self, data: List[str], progress_bar=True, **kwargs) -> List[List[float]]:
|
|
47
|
+
# the embed method returns a Iterable[np.ndarray], so we convert it to a list of lists
|
|
48
|
+
embeddings = []
|
|
49
|
+
embeddings_iterable = self.model.embed(data, **kwargs)
|
|
50
|
+
for np_array in tqdm(
|
|
51
|
+
embeddings_iterable, disable=not progress_bar, desc="Calculating embeddings", total=len(data)
|
|
52
|
+
):
|
|
53
|
+
embeddings.append(np_array.tolist())
|
|
54
|
+
return embeddings
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _FastembedSparseEmbeddingBackendFactory:
|
|
58
|
+
"""
|
|
59
|
+
Factory class to create instances of fastembed sparse embedding backends.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
_instances: ClassVar[Dict[str, "_FastembedSparseEmbeddingBackend"]] = {}
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def get_embedding_backend(
|
|
66
|
+
model_name: str,
|
|
67
|
+
cache_dir: Optional[str] = None,
|
|
68
|
+
threads: Optional[int] = None,
|
|
69
|
+
):
|
|
70
|
+
embedding_backend_id = f"{model_name}{cache_dir}{threads}"
|
|
71
|
+
|
|
72
|
+
if embedding_backend_id in _FastembedSparseEmbeddingBackendFactory._instances:
|
|
73
|
+
return _FastembedSparseEmbeddingBackendFactory._instances[embedding_backend_id]
|
|
74
|
+
|
|
75
|
+
embedding_backend = _FastembedSparseEmbeddingBackend(
|
|
76
|
+
model_name=model_name, cache_dir=cache_dir, threads=threads
|
|
77
|
+
)
|
|
78
|
+
_FastembedSparseEmbeddingBackendFactory._instances[embedding_backend_id] = embedding_backend
|
|
79
|
+
return embedding_backend
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class _FastembedSparseEmbeddingBackend:
|
|
83
|
+
"""
|
|
84
|
+
Class to manage fastembed sparse embeddings.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
model_name: str,
|
|
90
|
+
cache_dir: Optional[str] = None,
|
|
91
|
+
threads: Optional[int] = None,
|
|
92
|
+
):
|
|
93
|
+
self.model = SparseTextEmbedding(model_name=model_name, cache_dir=cache_dir, threads=threads)
|
|
94
|
+
|
|
95
|
+
def embed(self, data: List[List[str]], progress_bar=True, **kwargs) -> List[SparseEmbedding]:
|
|
96
|
+
# The embed method returns a Iterable[SparseEmbedding], so we convert to Haystack SparseEmbedding type.
|
|
97
|
+
# Each SparseEmbedding contains an `indices` key containing a list of int and
|
|
98
|
+
# an `values` key containing a list of floats.
|
|
99
|
+
|
|
100
|
+
sparse_embeddings = []
|
|
101
|
+
sparse_embeddings_iterable = self.model.embed(data, **kwargs)
|
|
102
|
+
for sparse_embedding in tqdm(
|
|
103
|
+
sparse_embeddings_iterable, disable=not progress_bar, desc="Calculating sparse embeddings", total=len(data)
|
|
104
|
+
):
|
|
105
|
+
sparse_embeddings.append(
|
|
106
|
+
SparseEmbedding(indices=sparse_embedding.indices.tolist(), values=sparse_embedding.values.tolist())
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return sparse_embeddings
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from haystack import Document, component, default_to_dict
|
|
4
|
+
|
|
5
|
+
from .embedding_backend.fastembed_backend import _FastembedSparseEmbeddingBackendFactory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@component
|
|
9
|
+
class FastembedSparseDocumentEmbedder:
|
|
10
|
+
"""
|
|
11
|
+
FastembedSparseDocumentEmbedder computes Document embeddings using Fastembed sparse models.
|
|
12
|
+
|
|
13
|
+
Usage example:
|
|
14
|
+
```python
|
|
15
|
+
# To use this component, install the "fastembed-haystack" package.
|
|
16
|
+
# pip install fastembed-haystack
|
|
17
|
+
|
|
18
|
+
from haystack_integrations.components.embedders.fastembed import FastembedSparseDocumentEmbedder
|
|
19
|
+
from haystack.dataclasses import Document
|
|
20
|
+
|
|
21
|
+
doc_embedder = FastembedSparseDocumentEmbedder(
|
|
22
|
+
model="prithvida/Splade_PP_en_v1",
|
|
23
|
+
batch_size=32,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
doc_embedder.warm_up()
|
|
27
|
+
|
|
28
|
+
# Text taken from PubMed QA Dataset (https://huggingface.co/datasets/pubmed_qa)
|
|
29
|
+
document_list = [
|
|
30
|
+
Document(
|
|
31
|
+
content="Oxidative stress generated within inflammatory joints can produce autoimmune phenomena and joint destruction. Radical species with oxidative activity, including reactive nitrogen species, represent mediators of inflammation and cartilage damage.",
|
|
32
|
+
meta={
|
|
33
|
+
"pubid": "25,445,628",
|
|
34
|
+
"long_answer": "yes",
|
|
35
|
+
},
|
|
36
|
+
),
|
|
37
|
+
Document(
|
|
38
|
+
content="Plasma levels of pancreatic polypeptide (PP) rise upon food intake. Although other pancreatic islet hormones, such as insulin and glucagon, have been extensively investigated, PP secretion and actions are still poorly understood.",
|
|
39
|
+
meta={
|
|
40
|
+
"pubid": "25,445,712",
|
|
41
|
+
"long_answer": "yes",
|
|
42
|
+
},
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
result = doc_embedder.run(document_list)
|
|
47
|
+
print(f"Document Text: {result['documents'][0].content}")
|
|
48
|
+
print(f"Document Embedding: {result['documents'][0].sparse_embedding}")
|
|
49
|
+
print(f"Embedding Dimension: {len(result['documents'][0].sparse_embedding)}")
|
|
50
|
+
```
|
|
51
|
+
""" # noqa: E501
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
model: str = "prithvida/Splade_PP_en_v1",
|
|
56
|
+
cache_dir: Optional[str] = None,
|
|
57
|
+
threads: Optional[int] = None,
|
|
58
|
+
batch_size: int = 32,
|
|
59
|
+
progress_bar: bool = True,
|
|
60
|
+
parallel: Optional[int] = None,
|
|
61
|
+
meta_fields_to_embed: Optional[List[str]] = None,
|
|
62
|
+
embedding_separator: str = "\n",
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
Create an FastembedDocumentEmbedder component.
|
|
66
|
+
|
|
67
|
+
:param model: Local path or name of the model in Hugging Face's model hub,
|
|
68
|
+
such as `prithvida/Splade_PP_en_v1`.
|
|
69
|
+
:param cache_dir: The path to the cache directory.
|
|
70
|
+
Can be set using the `FASTEMBED_CACHE_PATH` env variable.
|
|
71
|
+
Defaults to `fastembed_cache` in the system's temp directory.
|
|
72
|
+
:param threads: The number of threads single onnxruntime session can use.
|
|
73
|
+
:param batch_size: Number of strings to encode at once.
|
|
74
|
+
:param progress_bar: If `True`, displays progress bar during embedding.
|
|
75
|
+
:param parallel:
|
|
76
|
+
If > 1, data-parallel encoding will be used, recommended for offline encoding of large datasets.
|
|
77
|
+
If 0, use all available cores.
|
|
78
|
+
If None, don't use data-parallel processing, use default onnxruntime threading instead.
|
|
79
|
+
:param meta_fields_to_embed: List of meta fields that should be embedded along with the Document content.
|
|
80
|
+
:param embedding_separator: Separator used to concatenate the meta fields to the Document content.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
self.model_name = model
|
|
84
|
+
self.cache_dir = cache_dir
|
|
85
|
+
self.threads = threads
|
|
86
|
+
self.batch_size = batch_size
|
|
87
|
+
self.progress_bar = progress_bar
|
|
88
|
+
self.parallel = parallel
|
|
89
|
+
self.meta_fields_to_embed = meta_fields_to_embed or []
|
|
90
|
+
self.embedding_separator = embedding_separator
|
|
91
|
+
|
|
92
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
93
|
+
"""
|
|
94
|
+
Serializes the component to a dictionary.
|
|
95
|
+
:returns:
|
|
96
|
+
Dictionary with serialized data.
|
|
97
|
+
"""
|
|
98
|
+
return default_to_dict(
|
|
99
|
+
self,
|
|
100
|
+
model=self.model_name,
|
|
101
|
+
cache_dir=self.cache_dir,
|
|
102
|
+
threads=self.threads,
|
|
103
|
+
batch_size=self.batch_size,
|
|
104
|
+
progress_bar=self.progress_bar,
|
|
105
|
+
parallel=self.parallel,
|
|
106
|
+
meta_fields_to_embed=self.meta_fields_to_embed,
|
|
107
|
+
embedding_separator=self.embedding_separator,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def warm_up(self):
|
|
111
|
+
"""
|
|
112
|
+
Initializes the component.
|
|
113
|
+
"""
|
|
114
|
+
if not hasattr(self, "embedding_backend"):
|
|
115
|
+
self.embedding_backend = _FastembedSparseEmbeddingBackendFactory.get_embedding_backend(
|
|
116
|
+
model_name=self.model_name, cache_dir=self.cache_dir, threads=self.threads
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def _prepare_texts_to_embed(self, documents: List[Document]) -> List[str]:
|
|
120
|
+
texts_to_embed = []
|
|
121
|
+
for doc in documents:
|
|
122
|
+
meta_values_to_embed = [
|
|
123
|
+
str(doc.meta[key]) for key in self.meta_fields_to_embed if key in doc.meta and doc.meta[key] is not None
|
|
124
|
+
]
|
|
125
|
+
text_to_embed = self.embedding_separator.join([*meta_values_to_embed, doc.content or ""])
|
|
126
|
+
|
|
127
|
+
texts_to_embed.append(text_to_embed)
|
|
128
|
+
return texts_to_embed
|
|
129
|
+
|
|
130
|
+
@component.output_types(documents=List[Document])
|
|
131
|
+
def run(self, documents: List[Document]):
|
|
132
|
+
"""
|
|
133
|
+
Embeds a list of Documents.
|
|
134
|
+
|
|
135
|
+
:param documents: List of Documents to embed.
|
|
136
|
+
:returns: A dictionary with the following keys:
|
|
137
|
+
- `documents`: List of Documents with each Document's `sparse_embedding`
|
|
138
|
+
field set to the computed embeddings.
|
|
139
|
+
"""
|
|
140
|
+
if not isinstance(documents, list) or documents and not isinstance(documents[0], Document):
|
|
141
|
+
msg = (
|
|
142
|
+
"FastembedSparseDocumentEmbedder expects a list of Documents as input. "
|
|
143
|
+
"In case you want to embed a list of strings, please use the FastembedTextEmbedder."
|
|
144
|
+
)
|
|
145
|
+
raise TypeError(msg)
|
|
146
|
+
if not hasattr(self, "embedding_backend"):
|
|
147
|
+
msg = "The embedding model has not been loaded. Please call warm_up() before running."
|
|
148
|
+
raise RuntimeError(msg)
|
|
149
|
+
|
|
150
|
+
texts_to_embed = self._prepare_texts_to_embed(documents=documents)
|
|
151
|
+
embeddings = self.embedding_backend.embed(
|
|
152
|
+
texts_to_embed,
|
|
153
|
+
batch_size=self.batch_size,
|
|
154
|
+
show_progress_bar=self.progress_bar,
|
|
155
|
+
parallel=self.parallel,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
for doc, emb in zip(documents, embeddings):
|
|
159
|
+
doc.sparse_embedding = emb
|
|
160
|
+
return {"documents": documents}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from haystack import component, default_to_dict
|
|
4
|
+
from haystack.dataclasses.sparse_embedding import SparseEmbedding
|
|
5
|
+
|
|
6
|
+
from .embedding_backend.fastembed_backend import _FastembedSparseEmbeddingBackendFactory
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@component
|
|
10
|
+
class FastembedSparseTextEmbedder:
|
|
11
|
+
"""
|
|
12
|
+
FastembedSparseTextEmbedder computes string embedding using fastembed sparse models.
|
|
13
|
+
|
|
14
|
+
Usage example:
|
|
15
|
+
```python
|
|
16
|
+
# To use this component, install the "fastembed-haystack" package.
|
|
17
|
+
# pip install fastembed-haystack
|
|
18
|
+
|
|
19
|
+
from haystack_integrations.components.embedders.fastembed import FastembedSparseTextEmbedder
|
|
20
|
+
|
|
21
|
+
text = "It clearly says online this will work on a Mac OS system. The disk comes and it does not, only Windows. Do Not order this if you have a Mac!!"
|
|
22
|
+
|
|
23
|
+
text_embedder = FastembedSparseTextEmbedder(
|
|
24
|
+
model="prithvida/Splade_PP_en_v1"
|
|
25
|
+
)
|
|
26
|
+
text_embedder.warm_up()
|
|
27
|
+
|
|
28
|
+
embedding = text_embedder.run(text)["embedding"]
|
|
29
|
+
```
|
|
30
|
+
""" # noqa: E501
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
model: str = "prithvida/Splade_PP_en_v1",
|
|
35
|
+
cache_dir: Optional[str] = None,
|
|
36
|
+
threads: Optional[int] = None,
|
|
37
|
+
batch_size: int = 32,
|
|
38
|
+
progress_bar: bool = True,
|
|
39
|
+
parallel: Optional[int] = None,
|
|
40
|
+
):
|
|
41
|
+
"""
|
|
42
|
+
Create a FastembedSparseTextEmbedder component.
|
|
43
|
+
|
|
44
|
+
:param model: Local path or name of the model in Fastembed's model hub, such as `prithvida/Splade_PP_en_v1`
|
|
45
|
+
:param cache_dir: The path to the cache directory.
|
|
46
|
+
Can be set using the `FASTEMBED_CACHE_PATH` env variable.
|
|
47
|
+
Defaults to `fastembed_cache` in the system's temp directory.
|
|
48
|
+
:param threads: The number of threads single onnxruntime session can use. Defaults to None.
|
|
49
|
+
:param batch_size: Number of strings to encode at once.
|
|
50
|
+
:param progress_bar: If true, displays progress bar during embedding.
|
|
51
|
+
:param parallel:
|
|
52
|
+
If > 1, data-parallel encoding will be used, recommended for offline encoding of large datasets.
|
|
53
|
+
If 0, use all available cores.
|
|
54
|
+
If None, don't use data-parallel processing, use default onnxruntime threading instead.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
self.model_name = model
|
|
58
|
+
self.cache_dir = cache_dir
|
|
59
|
+
self.threads = threads
|
|
60
|
+
self.batch_size = batch_size
|
|
61
|
+
self.progress_bar = progress_bar
|
|
62
|
+
self.parallel = parallel
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
65
|
+
"""
|
|
66
|
+
Serializes the component to a dictionary.
|
|
67
|
+
|
|
68
|
+
:returns:
|
|
69
|
+
Dictionary with serialized data.
|
|
70
|
+
"""
|
|
71
|
+
return default_to_dict(
|
|
72
|
+
self,
|
|
73
|
+
model=self.model_name,
|
|
74
|
+
cache_dir=self.cache_dir,
|
|
75
|
+
threads=self.threads,
|
|
76
|
+
batch_size=self.batch_size,
|
|
77
|
+
progress_bar=self.progress_bar,
|
|
78
|
+
parallel=self.parallel,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def warm_up(self):
|
|
82
|
+
"""
|
|
83
|
+
Initializes the component.
|
|
84
|
+
"""
|
|
85
|
+
if not hasattr(self, "embedding_backend"):
|
|
86
|
+
self.embedding_backend = _FastembedSparseEmbeddingBackendFactory.get_embedding_backend(
|
|
87
|
+
model_name=self.model_name, cache_dir=self.cache_dir, threads=self.threads
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@component.output_types(sparse_embedding=SparseEmbedding)
|
|
91
|
+
def run(self, text: str):
|
|
92
|
+
"""
|
|
93
|
+
Embeds text using the Fastembed model.
|
|
94
|
+
|
|
95
|
+
:param text: A string to embed.
|
|
96
|
+
:returns: A dictionary with the following keys:
|
|
97
|
+
- `embedding`: A list of floats representing the embedding of the input text.
|
|
98
|
+
:raises TypeError: If the input is not a string.
|
|
99
|
+
:raises RuntimeError: If the embedding model has not been loaded.
|
|
100
|
+
"""
|
|
101
|
+
if not isinstance(text, str):
|
|
102
|
+
msg = (
|
|
103
|
+
"FastembedSparseTextEmbedder expects a string as input. "
|
|
104
|
+
"In case you want to embed a list of Documents, please use the FastembedDocumentEmbedder."
|
|
105
|
+
)
|
|
106
|
+
raise TypeError(msg)
|
|
107
|
+
if not hasattr(self, "embedding_backend"):
|
|
108
|
+
msg = "The embedding model has not been loaded. Please call warm_up() before running."
|
|
109
|
+
raise RuntimeError(msg)
|
|
110
|
+
|
|
111
|
+
embedding = self.embedding_backend.embed(
|
|
112
|
+
[text],
|
|
113
|
+
batch_size=self.batch_size,
|
|
114
|
+
show_progress_bar=self.progress_bar,
|
|
115
|
+
parallel=self.parallel,
|
|
116
|
+
)[0]
|
|
117
|
+
return {"sparse_embedding": embedding}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
from haystack import Document, default_from_dict
|
|
6
|
+
from haystack.dataclasses.sparse_embedding import SparseEmbedding
|
|
7
|
+
from haystack_integrations.components.embedders.fastembed.fastembed_sparse_document_embedder import (
|
|
8
|
+
FastembedSparseDocumentEmbedder,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestFastembedSparseDocumentEmbedderDoc:
|
|
13
|
+
def test_init_default(self):
|
|
14
|
+
"""
|
|
15
|
+
Test default initialization parameters for FastembedSparseDocumentEmbedder.
|
|
16
|
+
"""
|
|
17
|
+
embedder = FastembedSparseDocumentEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
18
|
+
assert embedder.model_name == "prithvida/Splade_PP_en_v1"
|
|
19
|
+
assert embedder.cache_dir is None
|
|
20
|
+
assert embedder.threads is None
|
|
21
|
+
assert embedder.batch_size == 32
|
|
22
|
+
assert embedder.progress_bar is True
|
|
23
|
+
assert embedder.parallel is None
|
|
24
|
+
assert embedder.meta_fields_to_embed == []
|
|
25
|
+
assert embedder.embedding_separator == "\n"
|
|
26
|
+
|
|
27
|
+
def test_init_with_parameters(self):
|
|
28
|
+
"""
|
|
29
|
+
Test custom initialization parameters for FastembedSparseDocumentEmbedder.
|
|
30
|
+
"""
|
|
31
|
+
embedder = FastembedSparseDocumentEmbedder(
|
|
32
|
+
model="prithvida/Splade_PP_en_v1",
|
|
33
|
+
cache_dir="fake_dir",
|
|
34
|
+
threads=2,
|
|
35
|
+
batch_size=64,
|
|
36
|
+
progress_bar=False,
|
|
37
|
+
parallel=1,
|
|
38
|
+
meta_fields_to_embed=["test_field"],
|
|
39
|
+
embedding_separator=" | ",
|
|
40
|
+
)
|
|
41
|
+
assert embedder.model_name == "prithvida/Splade_PP_en_v1"
|
|
42
|
+
assert embedder.cache_dir == "fake_dir"
|
|
43
|
+
assert embedder.threads == 2
|
|
44
|
+
assert embedder.batch_size == 64
|
|
45
|
+
assert embedder.progress_bar is False
|
|
46
|
+
assert embedder.parallel == 1
|
|
47
|
+
assert embedder.meta_fields_to_embed == ["test_field"]
|
|
48
|
+
assert embedder.embedding_separator == " | "
|
|
49
|
+
|
|
50
|
+
def test_to_dict(self):
|
|
51
|
+
"""
|
|
52
|
+
Test serialization of FastembedSparseDocumentEmbedder to a dictionary, using default initialization parameters.
|
|
53
|
+
"""
|
|
54
|
+
embedder = FastembedSparseDocumentEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
55
|
+
embedder_dict = embedder.to_dict()
|
|
56
|
+
assert embedder_dict == {
|
|
57
|
+
"type": "haystack_integrations.components.embedders.fastembed.fastembed_sparse_document_embedder.FastembedSparseDocumentEmbedder", # noqa
|
|
58
|
+
"init_parameters": {
|
|
59
|
+
"model": "prithvida/Splade_PP_en_v1",
|
|
60
|
+
"cache_dir": None,
|
|
61
|
+
"threads": None,
|
|
62
|
+
"batch_size": 32,
|
|
63
|
+
"progress_bar": True,
|
|
64
|
+
"parallel": None,
|
|
65
|
+
"embedding_separator": "\n",
|
|
66
|
+
"meta_fields_to_embed": [],
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def test_to_dict_with_custom_init_parameters(self):
|
|
71
|
+
"""
|
|
72
|
+
Test serialization of FastembedSparseDocumentEmbedder to a dictionary, using custom initialization parameters.
|
|
73
|
+
"""
|
|
74
|
+
embedder = FastembedSparseDocumentEmbedder(
|
|
75
|
+
model="prithvida/Splade_PP_en_v1",
|
|
76
|
+
cache_dir="fake_dir",
|
|
77
|
+
threads=2,
|
|
78
|
+
batch_size=64,
|
|
79
|
+
progress_bar=False,
|
|
80
|
+
parallel=1,
|
|
81
|
+
meta_fields_to_embed=["test_field"],
|
|
82
|
+
embedding_separator=" | ",
|
|
83
|
+
)
|
|
84
|
+
embedder_dict = embedder.to_dict()
|
|
85
|
+
assert embedder_dict == {
|
|
86
|
+
"type": "haystack_integrations.components.embedders.fastembed.fastembed_sparse_document_embedder.FastembedSparseDocumentEmbedder", # noqa
|
|
87
|
+
"init_parameters": {
|
|
88
|
+
"model": "prithvida/Splade_PP_en_v1",
|
|
89
|
+
"cache_dir": "fake_dir",
|
|
90
|
+
"threads": 2,
|
|
91
|
+
"batch_size": 64,
|
|
92
|
+
"progress_bar": False,
|
|
93
|
+
"parallel": 1,
|
|
94
|
+
"meta_fields_to_embed": ["test_field"],
|
|
95
|
+
"embedding_separator": " | ",
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
def test_from_dict(self):
|
|
100
|
+
"""
|
|
101
|
+
Test deserialization of FastembedSparseDocumentEmbedder from a dictionary,
|
|
102
|
+
using default initialization parameters.
|
|
103
|
+
"""
|
|
104
|
+
embedder_dict = {
|
|
105
|
+
"type": "haystack_integrations.components.embedders.fastembed.fastembed_sparse_document_embedder.FastembedSparseDocumentEmbedder", # noqa
|
|
106
|
+
"init_parameters": {
|
|
107
|
+
"model": "prithvida/Splade_PP_en_v1",
|
|
108
|
+
"cache_dir": None,
|
|
109
|
+
"threads": None,
|
|
110
|
+
"batch_size": 32,
|
|
111
|
+
"progress_bar": True,
|
|
112
|
+
"parallel": None,
|
|
113
|
+
"meta_fields_to_embed": [],
|
|
114
|
+
"embedding_separator": "\n",
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
embedder = default_from_dict(FastembedSparseDocumentEmbedder, embedder_dict)
|
|
118
|
+
assert embedder.model_name == "prithvida/Splade_PP_en_v1"
|
|
119
|
+
assert embedder.cache_dir is None
|
|
120
|
+
assert embedder.threads is None
|
|
121
|
+
assert embedder.batch_size == 32
|
|
122
|
+
assert embedder.progress_bar is True
|
|
123
|
+
assert embedder.parallel is None
|
|
124
|
+
assert embedder.meta_fields_to_embed == []
|
|
125
|
+
assert embedder.embedding_separator == "\n"
|
|
126
|
+
|
|
127
|
+
def test_from_dict_with_custom_init_parameters(self):
|
|
128
|
+
"""
|
|
129
|
+
Test deserialization of FastembedSparseDocumentEmbedder from a dictionary,
|
|
130
|
+
using custom initialization parameters.
|
|
131
|
+
"""
|
|
132
|
+
embedder_dict = {
|
|
133
|
+
"type": "haystack_integrations.components.embedders.fastembed.fastembed_sparse_document_embedder.FastembedSparseDocumentEmbedder", # noqa
|
|
134
|
+
"init_parameters": {
|
|
135
|
+
"model": "prithvida/Splade_PP_en_v1",
|
|
136
|
+
"cache_dir": "fake_dir",
|
|
137
|
+
"threads": 2,
|
|
138
|
+
"batch_size": 64,
|
|
139
|
+
"progress_bar": False,
|
|
140
|
+
"parallel": 1,
|
|
141
|
+
"meta_fields_to_embed": ["test_field"],
|
|
142
|
+
"embedding_separator": " | ",
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
embedder = default_from_dict(FastembedSparseDocumentEmbedder, embedder_dict)
|
|
146
|
+
assert embedder.model_name == "prithvida/Splade_PP_en_v1"
|
|
147
|
+
assert embedder.cache_dir == "fake_dir"
|
|
148
|
+
assert embedder.threads == 2
|
|
149
|
+
assert embedder.batch_size == 64
|
|
150
|
+
assert embedder.progress_bar is False
|
|
151
|
+
assert embedder.parallel == 1
|
|
152
|
+
assert embedder.meta_fields_to_embed == ["test_field"]
|
|
153
|
+
assert embedder.embedding_separator == " | "
|
|
154
|
+
|
|
155
|
+
@patch(
|
|
156
|
+
"haystack_integrations.components.embedders.fastembed.fastembed_sparse_document_embedder._FastembedSparseEmbeddingBackendFactory"
|
|
157
|
+
)
|
|
158
|
+
def test_warmup(self, mocked_factory):
|
|
159
|
+
"""
|
|
160
|
+
Test for checking embedder instances after warm-up.
|
|
161
|
+
"""
|
|
162
|
+
embedder = FastembedSparseDocumentEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
163
|
+
mocked_factory.get_embedding_backend.assert_not_called()
|
|
164
|
+
embedder.warm_up()
|
|
165
|
+
mocked_factory.get_embedding_backend.assert_called_once_with(
|
|
166
|
+
model_name="prithvida/Splade_PP_en_v1", cache_dir=None, threads=None
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@patch(
|
|
170
|
+
"haystack_integrations.components.embedders.fastembed.fastembed_sparse_document_embedder._FastembedSparseEmbeddingBackendFactory"
|
|
171
|
+
)
|
|
172
|
+
def test_warmup_does_not_reload(self, mocked_factory):
|
|
173
|
+
"""
|
|
174
|
+
Test for checking backend instances after multiple warm-ups.
|
|
175
|
+
"""
|
|
176
|
+
embedder = FastembedSparseDocumentEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
177
|
+
mocked_factory.get_embedding_backend.assert_not_called()
|
|
178
|
+
embedder.warm_up()
|
|
179
|
+
embedder.warm_up()
|
|
180
|
+
mocked_factory.get_embedding_backend.assert_called_once()
|
|
181
|
+
|
|
182
|
+
def _generate_mocked_sparse_embedding(self, n):
|
|
183
|
+
list_of_sparse_vectors = []
|
|
184
|
+
for _ in range(n):
|
|
185
|
+
random_indice_length = np.random.randint(3, 15)
|
|
186
|
+
data = {
|
|
187
|
+
"indices": list(range(random_indice_length)),
|
|
188
|
+
"values": [np.random.random_sample() for _ in range(random_indice_length)],
|
|
189
|
+
}
|
|
190
|
+
list_of_sparse_vectors.append(data)
|
|
191
|
+
return list_of_sparse_vectors
|
|
192
|
+
|
|
193
|
+
def test_embed(self):
|
|
194
|
+
"""
|
|
195
|
+
Test for checking output dimensions and embedding dimensions.
|
|
196
|
+
"""
|
|
197
|
+
embedder = FastembedSparseDocumentEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
198
|
+
embedder.embedding_backend = MagicMock()
|
|
199
|
+
embedder.embedding_backend.embed = lambda x, **kwargs: self._generate_mocked_sparse_embedding( # noqa: ARG005
|
|
200
|
+
len(x)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
documents = [Document(content=f"Sample-document text {i}") for i in range(5)]
|
|
204
|
+
|
|
205
|
+
result = embedder.run(documents=documents)
|
|
206
|
+
|
|
207
|
+
assert isinstance(result["documents"], list)
|
|
208
|
+
assert len(result["documents"]) == len(documents)
|
|
209
|
+
for doc in result["documents"]:
|
|
210
|
+
assert isinstance(doc, Document)
|
|
211
|
+
assert isinstance(doc.sparse_embedding, dict)
|
|
212
|
+
assert isinstance(doc.sparse_embedding["indices"], list)
|
|
213
|
+
assert isinstance(doc.sparse_embedding["indices"][0], int)
|
|
214
|
+
assert isinstance(doc.sparse_embedding["values"], list)
|
|
215
|
+
assert isinstance(doc.sparse_embedding["values"][0], float)
|
|
216
|
+
|
|
217
|
+
def test_embed_incorrect_input_format(self):
|
|
218
|
+
"""
|
|
219
|
+
Test for checking incorrect input format when creating embedding.
|
|
220
|
+
"""
|
|
221
|
+
embedder = FastembedSparseDocumentEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
222
|
+
|
|
223
|
+
string_input = "text"
|
|
224
|
+
list_integers_input = [1, 2, 3]
|
|
225
|
+
|
|
226
|
+
with pytest.raises(
|
|
227
|
+
TypeError,
|
|
228
|
+
match="FastembedSparseDocumentEmbedder expects a list of Documents as input.",
|
|
229
|
+
):
|
|
230
|
+
embedder.run(documents=string_input)
|
|
231
|
+
|
|
232
|
+
with pytest.raises(
|
|
233
|
+
TypeError,
|
|
234
|
+
match="FastembedSparseDocumentEmbedder expects a list of Documents as input.",
|
|
235
|
+
):
|
|
236
|
+
embedder.run(documents=list_integers_input)
|
|
237
|
+
|
|
238
|
+
def test_embed_metadata(self):
|
|
239
|
+
"""
|
|
240
|
+
Test for checking output dimensions and embedding dimensions for documents
|
|
241
|
+
with a custom instruction and metadata.
|
|
242
|
+
"""
|
|
243
|
+
embedder = FastembedSparseDocumentEmbedder(
|
|
244
|
+
model="model",
|
|
245
|
+
meta_fields_to_embed=["meta_field"],
|
|
246
|
+
embedding_separator="\n",
|
|
247
|
+
)
|
|
248
|
+
embedder.embedding_backend = MagicMock()
|
|
249
|
+
|
|
250
|
+
documents = [Document(content=f"document-number {i}", meta={"meta_field": f"meta_value {i}"}) for i in range(5)]
|
|
251
|
+
|
|
252
|
+
embedder.run(documents=documents)
|
|
253
|
+
|
|
254
|
+
embedder.embedding_backend.embed.assert_called_once_with(
|
|
255
|
+
[
|
|
256
|
+
"meta_value 0\ndocument-number 0",
|
|
257
|
+
"meta_value 1\ndocument-number 1",
|
|
258
|
+
"meta_value 2\ndocument-number 2",
|
|
259
|
+
"meta_value 3\ndocument-number 3",
|
|
260
|
+
"meta_value 4\ndocument-number 4",
|
|
261
|
+
],
|
|
262
|
+
batch_size=32,
|
|
263
|
+
show_progress_bar=True,
|
|
264
|
+
parallel=None,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
@pytest.mark.integration
|
|
268
|
+
def test_run(self):
|
|
269
|
+
embedder = FastembedSparseDocumentEmbedder(
|
|
270
|
+
model="prithvida/Splade_PP_en_v1",
|
|
271
|
+
)
|
|
272
|
+
embedder.warm_up()
|
|
273
|
+
|
|
274
|
+
doc = Document(content="Parton energy loss in QCD matter")
|
|
275
|
+
|
|
276
|
+
result = embedder.run(documents=[doc])
|
|
277
|
+
embedding = result["documents"][0].sparse_embedding
|
|
278
|
+
embedding_dict = embedding.to_dict()
|
|
279
|
+
assert isinstance(embedding, SparseEmbedding)
|
|
280
|
+
assert isinstance(embedding_dict["indices"], list)
|
|
281
|
+
assert isinstance(embedding_dict["values"], list)
|
|
282
|
+
assert isinstance(embedding_dict["indices"][0], int)
|
|
283
|
+
assert isinstance(embedding_dict["values"][0], float)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
from haystack import default_from_dict
|
|
6
|
+
from haystack.dataclasses.sparse_embedding import SparseEmbedding
|
|
7
|
+
from haystack_integrations.components.embedders.fastembed.fastembed_sparse_text_embedder import (
|
|
8
|
+
FastembedSparseTextEmbedder,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestFastembedSparseTextEmbedder:
|
|
13
|
+
def test_init_default(self):
|
|
14
|
+
"""
|
|
15
|
+
Test default initialization parameters for FastembedSparseTextEmbedder.
|
|
16
|
+
"""
|
|
17
|
+
embedder = FastembedSparseTextEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
18
|
+
assert embedder.model_name == "prithvida/Splade_PP_en_v1"
|
|
19
|
+
assert embedder.cache_dir is None
|
|
20
|
+
assert embedder.threads is None
|
|
21
|
+
assert embedder.batch_size == 32
|
|
22
|
+
assert embedder.progress_bar is True
|
|
23
|
+
assert embedder.parallel is None
|
|
24
|
+
|
|
25
|
+
def test_init_with_parameters(self):
|
|
26
|
+
"""
|
|
27
|
+
Test custom initialization parameters for FastembedSparseTextEmbedder.
|
|
28
|
+
"""
|
|
29
|
+
embedder = FastembedSparseTextEmbedder(
|
|
30
|
+
model="prithvida/Splade_PP_en_v1",
|
|
31
|
+
cache_dir="fake_dir",
|
|
32
|
+
threads=2,
|
|
33
|
+
batch_size=64,
|
|
34
|
+
progress_bar=False,
|
|
35
|
+
parallel=1,
|
|
36
|
+
)
|
|
37
|
+
assert embedder.model_name == "prithvida/Splade_PP_en_v1"
|
|
38
|
+
assert embedder.cache_dir == "fake_dir"
|
|
39
|
+
assert embedder.threads == 2
|
|
40
|
+
assert embedder.batch_size == 64
|
|
41
|
+
assert embedder.progress_bar is False
|
|
42
|
+
assert embedder.parallel == 1
|
|
43
|
+
|
|
44
|
+
def test_to_dict(self):
|
|
45
|
+
"""
|
|
46
|
+
Test serialization of FastembedSparseTextEmbedder to a dictionary, using default initialization parameters.
|
|
47
|
+
"""
|
|
48
|
+
embedder = FastembedSparseTextEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
49
|
+
embedder_dict = embedder.to_dict()
|
|
50
|
+
assert embedder_dict == {
|
|
51
|
+
"type": "haystack_integrations.components.embedders.fastembed.fastembed_sparse_text_embedder.FastembedSparseTextEmbedder", # noqa
|
|
52
|
+
"init_parameters": {
|
|
53
|
+
"model": "prithvida/Splade_PP_en_v1",
|
|
54
|
+
"cache_dir": None,
|
|
55
|
+
"threads": None,
|
|
56
|
+
"batch_size": 32,
|
|
57
|
+
"progress_bar": True,
|
|
58
|
+
"parallel": None,
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def test_to_dict_with_custom_init_parameters(self):
|
|
63
|
+
"""
|
|
64
|
+
Test serialization of FastembedSparseTextEmbedder to a dictionary, using custom initialization parameters.
|
|
65
|
+
"""
|
|
66
|
+
embedder = FastembedSparseTextEmbedder(
|
|
67
|
+
model="prithvida/Splade_PP_en_v1",
|
|
68
|
+
cache_dir="fake_dir",
|
|
69
|
+
threads=2,
|
|
70
|
+
batch_size=64,
|
|
71
|
+
progress_bar=False,
|
|
72
|
+
parallel=1,
|
|
73
|
+
)
|
|
74
|
+
embedder_dict = embedder.to_dict()
|
|
75
|
+
assert embedder_dict == {
|
|
76
|
+
"type": "haystack_integrations.components.embedders.fastembed.fastembed_sparse_text_embedder.FastembedSparseTextEmbedder", # noqa
|
|
77
|
+
"init_parameters": {
|
|
78
|
+
"model": "prithvida/Splade_PP_en_v1",
|
|
79
|
+
"cache_dir": "fake_dir",
|
|
80
|
+
"threads": 2,
|
|
81
|
+
"batch_size": 64,
|
|
82
|
+
"progress_bar": False,
|
|
83
|
+
"parallel": 1,
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def test_from_dict(self):
|
|
88
|
+
"""
|
|
89
|
+
Test deserialization of FastembedSparseTextEmbedder from a dictionary, using default initialization parameters.
|
|
90
|
+
"""
|
|
91
|
+
embedder_dict = {
|
|
92
|
+
"type": "haystack_integrations.components.embedders.fastembed.fastembed_sparse_text_embedder.FastembedSparseTextEmbedder", # noqa
|
|
93
|
+
"init_parameters": {
|
|
94
|
+
"model": "prithvida/Splade_PP_en_v1",
|
|
95
|
+
"cache_dir": None,
|
|
96
|
+
"threads": None,
|
|
97
|
+
"batch_size": 32,
|
|
98
|
+
"progress_bar": True,
|
|
99
|
+
"parallel": None,
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
embedder = default_from_dict(FastembedSparseTextEmbedder, embedder_dict)
|
|
103
|
+
assert embedder.model_name == "prithvida/Splade_PP_en_v1"
|
|
104
|
+
assert embedder.cache_dir is None
|
|
105
|
+
assert embedder.threads is None
|
|
106
|
+
assert embedder.batch_size == 32
|
|
107
|
+
assert embedder.progress_bar is True
|
|
108
|
+
assert embedder.parallel is None
|
|
109
|
+
|
|
110
|
+
def test_from_dict_with_custom_init_parameters(self):
|
|
111
|
+
"""
|
|
112
|
+
Test deserialization of FastembedSparseTextEmbedder from a dictionary, using custom initialization parameters.
|
|
113
|
+
"""
|
|
114
|
+
embedder_dict = {
|
|
115
|
+
"type": "haystack_integrations.components.embedders.fastembed.fastembed_sparse_text_embedder.FastembedSparseTextEmbedder", # noqa
|
|
116
|
+
"init_parameters": {
|
|
117
|
+
"model": "prithvida/Splade_PP_en_v1",
|
|
118
|
+
"cache_dir": "fake_dir",
|
|
119
|
+
"threads": 2,
|
|
120
|
+
"batch_size": 64,
|
|
121
|
+
"progress_bar": False,
|
|
122
|
+
"parallel": 1,
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
embedder = default_from_dict(FastembedSparseTextEmbedder, embedder_dict)
|
|
126
|
+
assert embedder.model_name == "prithvida/Splade_PP_en_v1"
|
|
127
|
+
assert embedder.cache_dir == "fake_dir"
|
|
128
|
+
assert embedder.threads == 2
|
|
129
|
+
assert embedder.batch_size == 64
|
|
130
|
+
assert embedder.progress_bar is False
|
|
131
|
+
assert embedder.parallel == 1
|
|
132
|
+
|
|
133
|
+
@patch(
|
|
134
|
+
"haystack_integrations.components.embedders.fastembed.fastembed_sparse_text_embedder._FastembedSparseEmbeddingBackendFactory"
|
|
135
|
+
)
|
|
136
|
+
def test_warmup(self, mocked_factory):
|
|
137
|
+
"""
|
|
138
|
+
Test for checking embedder instances after warm-up.
|
|
139
|
+
"""
|
|
140
|
+
embedder = FastembedSparseTextEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
141
|
+
mocked_factory.get_embedding_backend.assert_not_called()
|
|
142
|
+
embedder.warm_up()
|
|
143
|
+
mocked_factory.get_embedding_backend.assert_called_once_with(
|
|
144
|
+
model_name="prithvida/Splade_PP_en_v1", cache_dir=None, threads=None
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
@patch(
|
|
148
|
+
"haystack_integrations.components.embedders.fastembed.fastembed_sparse_text_embedder._FastembedSparseEmbeddingBackendFactory"
|
|
149
|
+
)
|
|
150
|
+
def test_warmup_does_not_reload(self, mocked_factory):
|
|
151
|
+
"""
|
|
152
|
+
Test for checking backend instances after multiple warm-ups.
|
|
153
|
+
"""
|
|
154
|
+
embedder = FastembedSparseTextEmbedder(model="prithvida/Splade_PP_en_v1")
|
|
155
|
+
mocked_factory.get_embedding_backend.assert_not_called()
|
|
156
|
+
embedder.warm_up()
|
|
157
|
+
embedder.warm_up()
|
|
158
|
+
mocked_factory.get_embedding_backend.assert_called_once()
|
|
159
|
+
|
|
160
|
+
def _generate_mocked_sparse_embedding(self, n):
|
|
161
|
+
list_of_sparse_vectors = []
|
|
162
|
+
for _ in range(n):
|
|
163
|
+
random_indice_length = np.random.randint(3, 15)
|
|
164
|
+
data = {
|
|
165
|
+
"indices": list(range(random_indice_length)),
|
|
166
|
+
"values": [np.random.random_sample() for _ in range(random_indice_length)],
|
|
167
|
+
}
|
|
168
|
+
list_of_sparse_vectors.append(data)
|
|
169
|
+
|
|
170
|
+
return list_of_sparse_vectors
|
|
171
|
+
|
|
172
|
+
def test_embed(self):
|
|
173
|
+
"""
|
|
174
|
+
Test for checking output dimensions and embedding dimensions.
|
|
175
|
+
"""
|
|
176
|
+
embedder = FastembedSparseTextEmbedder(model="BAAI/bge-base-en-v1.5")
|
|
177
|
+
embedder.embedding_backend = MagicMock()
|
|
178
|
+
embedder.embedding_backend.embed = lambda x, **kwargs: self._generate_mocked_sparse_embedding( # noqa: ARG005
|
|
179
|
+
len(x)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
text = "Good text to embed"
|
|
183
|
+
|
|
184
|
+
result = embedder.run(text=text)
|
|
185
|
+
embedding = result["sparse_embedding"]
|
|
186
|
+
assert isinstance(embedding, dict)
|
|
187
|
+
assert isinstance(embedding["indices"], list)
|
|
188
|
+
assert isinstance(embedding["indices"][0], int)
|
|
189
|
+
assert isinstance(embedding["values"], list)
|
|
190
|
+
assert isinstance(embedding["values"][0], float)
|
|
191
|
+
|
|
192
|
+
def test_run_wrong_incorrect_format(self):
|
|
193
|
+
"""
|
|
194
|
+
Test for checking incorrect input format when creating embedding.
|
|
195
|
+
"""
|
|
196
|
+
embedder = FastembedSparseTextEmbedder(model="BAAI/bge-base-en-v1.5")
|
|
197
|
+
embedder.embedding_backend = MagicMock()
|
|
198
|
+
|
|
199
|
+
list_integers_input = [1, 2, 3]
|
|
200
|
+
|
|
201
|
+
with pytest.raises(TypeError, match="FastembedSparseTextEmbedder expects a string as input"):
|
|
202
|
+
embedder.run(text=list_integers_input)
|
|
203
|
+
|
|
204
|
+
@pytest.mark.integration
|
|
205
|
+
def test_run(self):
|
|
206
|
+
embedder = FastembedSparseTextEmbedder(
|
|
207
|
+
model="prithvida/Splade_PP_en_v1",
|
|
208
|
+
)
|
|
209
|
+
embedder.warm_up()
|
|
210
|
+
|
|
211
|
+
text = "Parton energy loss in QCD matter"
|
|
212
|
+
|
|
213
|
+
result = embedder.run(text=text)
|
|
214
|
+
embedding = result["sparse_embedding"]
|
|
215
|
+
embedding_dict = embedding.to_dict()
|
|
216
|
+
assert isinstance(embedding, SparseEmbedding)
|
|
217
|
+
assert isinstance(embedding_dict["indices"], list)
|
|
218
|
+
assert isinstance(embedding_dict["values"], list)
|
|
219
|
+
assert isinstance(embedding_dict["indices"][0], int)
|
|
220
|
+
assert isinstance(embedding_dict["values"][0], float)
|
fastembed_haystack-0.0.6/src/haystack_integrations/components/embedders/fastembed/__init__.py
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2024-present deepset GmbH <info@deepset.ai>
|
|
2
|
-
#
|
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
from .fastembed_document_embedder import FastembedDocumentEmbedder
|
|
5
|
-
from .fastembed_text_embedder import FastembedTextEmbedder
|
|
6
|
-
|
|
7
|
-
__all__ = ["FastembedDocumentEmbedder", "FastembedTextEmbedder"]
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
from typing import ClassVar, Dict, List, Optional
|
|
2
|
-
|
|
3
|
-
from tqdm import tqdm
|
|
4
|
-
|
|
5
|
-
from fastembed import TextEmbedding
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class _FastembedEmbeddingBackendFactory:
|
|
9
|
-
"""
|
|
10
|
-
Factory class to create instances of fastembed embedding backends.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
_instances: ClassVar[Dict[str, "_FastembedEmbeddingBackend"]] = {}
|
|
14
|
-
|
|
15
|
-
@staticmethod
|
|
16
|
-
def get_embedding_backend(
|
|
17
|
-
model_name: str,
|
|
18
|
-
cache_dir: Optional[str] = None,
|
|
19
|
-
threads: Optional[int] = None,
|
|
20
|
-
):
|
|
21
|
-
embedding_backend_id = f"{model_name}{cache_dir}{threads}"
|
|
22
|
-
|
|
23
|
-
if embedding_backend_id in _FastembedEmbeddingBackendFactory._instances:
|
|
24
|
-
return _FastembedEmbeddingBackendFactory._instances[embedding_backend_id]
|
|
25
|
-
|
|
26
|
-
embedding_backend = _FastembedEmbeddingBackend(model_name=model_name, cache_dir=cache_dir, threads=threads)
|
|
27
|
-
_FastembedEmbeddingBackendFactory._instances[embedding_backend_id] = embedding_backend
|
|
28
|
-
return embedding_backend
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class _FastembedEmbeddingBackend:
|
|
32
|
-
"""
|
|
33
|
-
Class to manage fastembed embeddings.
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
def __init__(
|
|
37
|
-
self,
|
|
38
|
-
model_name: str,
|
|
39
|
-
cache_dir: Optional[str] = None,
|
|
40
|
-
threads: Optional[int] = None,
|
|
41
|
-
):
|
|
42
|
-
self.model = TextEmbedding(model_name=model_name, cache_dir=cache_dir, threads=threads)
|
|
43
|
-
|
|
44
|
-
def embed(self, data: List[str], progress_bar=True, **kwargs) -> List[List[float]]:
|
|
45
|
-
# the embed method returns a Iterable[np.ndarray], so we convert it to a list of lists
|
|
46
|
-
embeddings = []
|
|
47
|
-
embeddings_iterable = self.model.embed(data, **kwargs)
|
|
48
|
-
for np_array in tqdm(
|
|
49
|
-
embeddings_iterable, disable=not progress_bar, desc="Calculating embeddings", total=len(data)
|
|
50
|
-
):
|
|
51
|
-
embeddings.append(np_array.tolist())
|
|
52
|
-
return embeddings
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastembed_haystack-0.0.6 → fastembed_haystack-0.1.0}/tests/test_fastembed_document_embedder.py
RENAMED
|
File without changes
|
|
File without changes
|