weaviate-haystack 7.8.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.
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/CHANGELOG.md +70 -14
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/PKG-INFO +3 -4
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/docker-compose.yml +14 -2
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/pyproject.toml +2 -3
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/weaviate/embedding_retriever.py +4 -4
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/weaviate/_filters.py +57 -39
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/weaviate/document_store.py +57 -6
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_bm25_retriever.py +20 -5
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_bm25_retriever_async.py +4 -5
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_document_store.py +212 -104
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_embedding_retriever.py +45 -7
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_embedding_retriever_async.py +29 -6
- weaviate_haystack-8.0.0/tests/test_filters.py +191 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_hybrid_retriever.py +32 -16
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_hybrid_retriever_async.py +7 -8
- weaviate_haystack-8.0.0/tests/test_retrieval.py +125 -0
- weaviate_haystack-7.8.0/tests/test_filters.py +0 -72
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/.gitignore +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/LICENSE.txt +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/README.md +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/pydoc/config_docusaurus.yml +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/weaviate/__init__.py +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/weaviate/bm25_retriever.py +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/weaviate/hybrid_retriever.py +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/py.typed +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/weaviate/__init__.py +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/weaviate/auth.py +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/__init__.py +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/conftest.py +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_auth.py +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_document_store_async.py +0 -0
- {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_files/robot1.jpg +0 -0
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/weaviate-v7.9.0] - 2026-09-21
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- Send X-Weaviate-Client-Integration telemetry header (#3954)
|
|
8
|
+
|
|
9
|
+
### ⚙️ CI
|
|
10
|
+
|
|
11
|
+
- Improve changelog generation; fix existing changelogs (#3883)
|
|
12
|
+
|
|
13
|
+
### 🧹 Chores
|
|
14
|
+
|
|
15
|
+
- Raise weaviate-client floor and test against Weaviate 1.39 (#3956)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## [integrations/weaviate-v7.8.0] - 2026-09-01
|
|
19
|
+
|
|
20
|
+
### 🐛 Bug Fixes
|
|
21
|
+
|
|
22
|
+
- `Weaviate` match `search_term` against metadata field value, not content + default size value set to 10 (#3643)
|
|
23
|
+
- Fix new issues raised by ruff 0.16.0 (#3670)
|
|
24
|
+
- `WeaviateDocumentStore` get_metadata_field_unique_values setting list of values to return to `Any` (#3708)
|
|
25
|
+
- Weaviate adding optional parameter `filters` to `get_metadata_field_unique_values()` (#3741)
|
|
26
|
+
- Standardize license files (#3771)
|
|
27
|
+
|
|
28
|
+
### 🧪 Testing
|
|
29
|
+
|
|
30
|
+
- `WeaviateDocumentStore` removing duplicated tests already covered by haystack Mixin tests (#3787)
|
|
31
|
+
|
|
32
|
+
|
|
3
33
|
## [integrations/weaviate-v7.7.0] - 2026-07-22
|
|
4
34
|
|
|
5
35
|
### 🚜 Refactor
|
|
@@ -207,21 +237,20 @@
|
|
|
207
237
|
|
|
208
238
|
### 🧹 Chores
|
|
209
239
|
|
|
210
|
-
- Weaviate - remove legacy filter support (#1070)
|
|
211
240
|
- Update changelog after removing legacy filters (#1083)
|
|
212
241
|
- Update ruff linting scripts and settings (#1105)
|
|
213
242
|
|
|
243
|
+
## [integrations/weaviate-v3.0.0] - 2024-09-12
|
|
214
244
|
|
|
215
|
-
|
|
245
|
+
### 🧹 Chores
|
|
216
246
|
|
|
217
|
-
|
|
247
|
+
- Weaviate - remove legacy filter support (#1070)
|
|
218
248
|
|
|
219
|
-
|
|
249
|
+
|
|
250
|
+
## [integrations/weaviate-v2.2.1] - 2024-09-07
|
|
220
251
|
|
|
221
252
|
### 🐛 Bug Fixes
|
|
222
253
|
|
|
223
|
-
- Weaviate filter error (#811)
|
|
224
|
-
- Fix connection to Weaviate Cloud Service (#624)
|
|
225
254
|
- Pin weaviate-client (#1046)
|
|
226
255
|
- Weaviate - fix connection issues with some WCS URLs (#1058)
|
|
227
256
|
|
|
@@ -229,9 +258,27 @@
|
|
|
229
258
|
|
|
230
259
|
- Do not retry tests in `hatch run test` command (#954)
|
|
231
260
|
|
|
232
|
-
###
|
|
261
|
+
### 🌀 Miscellaneous
|
|
233
262
|
|
|
234
|
-
-
|
|
263
|
+
- Chore: weaviate - ruff update, don't ruff tests (#986)
|
|
264
|
+
|
|
265
|
+
## [integrations/weaviate-v2.2.0] - 2024-08-02
|
|
266
|
+
|
|
267
|
+
### 🌀 Miscellaneous
|
|
268
|
+
|
|
269
|
+
- Fix: Weaviate: Use collections.list_all instead of collections._get_all (#921)
|
|
270
|
+
|
|
271
|
+
## [integrations/weaviate-v2.1.3] - 2024-07-17
|
|
272
|
+
|
|
273
|
+
### 🌀 Miscellaneous
|
|
274
|
+
|
|
275
|
+
- Fix: `weaviate` - Fallback to default filter policy when deserializing retrievers without the init parameter (#903)
|
|
276
|
+
|
|
277
|
+
## [integrations/weaviate-v2.1.2] - 2024-07-15
|
|
278
|
+
|
|
279
|
+
### 🚀 Features
|
|
280
|
+
|
|
281
|
+
- Add filter_policy to weaviate integration (#824)
|
|
235
282
|
|
|
236
283
|
### 🧹 Chores
|
|
237
284
|
|
|
@@ -239,15 +286,24 @@
|
|
|
239
286
|
|
|
240
287
|
### 🌀 Miscellaneous
|
|
241
288
|
|
|
289
|
+
- Chore: Minor retriever pydoc fix (#884)
|
|
290
|
+
|
|
291
|
+
## [integrations/weaviate-v2.1.1] - 2024-06-25
|
|
292
|
+
|
|
293
|
+
### 🐛 Bug Fixes
|
|
294
|
+
|
|
295
|
+
- Weaviate filter error (#811)
|
|
296
|
+
- Fix connection to Weaviate Cloud Service (#624)
|
|
297
|
+
|
|
298
|
+
### ⚙️ CI
|
|
299
|
+
|
|
300
|
+
- Retry tests to reduce flakyness (#836)
|
|
301
|
+
|
|
302
|
+
### 🌀 Miscellaneous
|
|
303
|
+
|
|
242
304
|
- Handle connection to WCS and add tests
|
|
243
305
|
- Revert "Handle connection to WCS and add tests"
|
|
244
|
-
|
|
245
|
-
This reverts commit f48802b2ce612896fd06a13cf33dffd9f77a8859.
|
|
246
306
|
- Ci: install `pytest-rerunfailures` where needed; add retry config to `test-cov` script (#845)
|
|
247
|
-
- Chore: Minor retriever pydoc fix (#884)
|
|
248
|
-
- Fix: `weaviate` - Fallback to default filter policy when deserializing retrievers without the init parameter (#903)
|
|
249
|
-
- Fix: Weaviate: Use collections.list_all instead of collections._get_all (#921)
|
|
250
|
-
- Chore: weaviate - ruff update, don't ruff tests (#986)
|
|
251
307
|
|
|
252
308
|
## [integrations/weaviate-v2.1.0] - 2024-06-10
|
|
253
309
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: weaviate-haystack
|
|
3
|
-
Version:
|
|
3
|
+
Version: 8.0.0
|
|
4
4
|
Summary: An integration of Weaviate vector database with Haystack
|
|
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/weaviate/README.md
|
|
@@ -11,17 +11,16 @@ License-File: LICENSE.txt
|
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
13
|
Classifier: Programming Language :: Python
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
18
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
20
19
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
21
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.11
|
|
22
21
|
Requires-Dist: haystack-ai>=2.28.0
|
|
23
22
|
Requires-Dist: python-dateutil>=2.7.0
|
|
24
|
-
Requires-Dist: weaviate-client>=4.
|
|
23
|
+
Requires-Dist: weaviate-client>=4.23.0
|
|
25
24
|
Description-Content-Type: text/markdown
|
|
26
25
|
|
|
27
26
|
# weaviate-haystack
|
|
@@ -7,7 +7,7 @@ services:
|
|
|
7
7
|
- '8080'
|
|
8
8
|
- --scheme
|
|
9
9
|
- http
|
|
10
|
-
image: semitechnologies/weaviate:1.
|
|
10
|
+
image: cr.weaviate.io/semitechnologies/weaviate:1.39.4
|
|
11
11
|
ports:
|
|
12
12
|
- 8080:8080
|
|
13
13
|
- 50051:50051
|
|
@@ -18,4 +18,16 @@ services:
|
|
|
18
18
|
PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
|
|
19
19
|
DEFAULT_VECTORIZER_MODULE: 'none'
|
|
20
20
|
ENABLE_MODULES: ''
|
|
21
|
-
CLUSTER_HOSTNAME: 'node1'
|
|
21
|
+
CLUSTER_HOSTNAME: 'node1'
|
|
22
|
+
healthcheck:
|
|
23
|
+
test:
|
|
24
|
+
- CMD
|
|
25
|
+
- busybox
|
|
26
|
+
- wget
|
|
27
|
+
- -q
|
|
28
|
+
- -O
|
|
29
|
+
- '-'
|
|
30
|
+
- http://127.0.0.1:8080/v1/.well-known/ready
|
|
31
|
+
interval: 5s
|
|
32
|
+
timeout: 3s
|
|
33
|
+
retries: 20
|
|
@@ -7,7 +7,7 @@ name = "weaviate-haystack"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "An integration of Weaviate vector database with Haystack"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
keywords = []
|
|
13
13
|
authors = [{ name = "deepset GmbH", email = "info@deepset.ai" }]
|
|
@@ -15,7 +15,6 @@ classifiers = [
|
|
|
15
15
|
"License :: OSI Approved :: Apache Software License",
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
17
17
|
"Programming Language :: Python",
|
|
18
|
-
"Programming Language :: Python :: 3.10",
|
|
19
18
|
"Programming Language :: Python :: 3.11",
|
|
20
19
|
"Programming Language :: Python :: 3.12",
|
|
21
20
|
"Programming Language :: Python :: 3.13",
|
|
@@ -25,7 +24,7 @@ classifiers = [
|
|
|
25
24
|
]
|
|
26
25
|
dependencies = [
|
|
27
26
|
"haystack-ai>=2.28.0",
|
|
28
|
-
"weaviate-client>=4.
|
|
27
|
+
"weaviate-client>=4.23.0",
|
|
29
28
|
"python-dateutil>=2.7.0",
|
|
30
29
|
]
|
|
31
30
|
|
|
@@ -143,8 +143,8 @@ class WeaviateEmbeddingRetriever:
|
|
|
143
143
|
filters = apply_filter_policy(self._filter_policy, self._filters, filters)
|
|
144
144
|
top_k = top_k or self._top_k
|
|
145
145
|
|
|
146
|
-
distance = distance
|
|
147
|
-
certainty = certainty
|
|
146
|
+
distance = self._distance if distance is None else distance
|
|
147
|
+
certainty = self._certainty if certainty is None else certainty
|
|
148
148
|
if distance is not None and certainty is not None:
|
|
149
149
|
msg = f"Can't use 'distance' ({distance}) and 'certainty' ({certainty}) parameters together"
|
|
150
150
|
raise ValueError(msg)
|
|
@@ -191,8 +191,8 @@ class WeaviateEmbeddingRetriever:
|
|
|
191
191
|
filters = apply_filter_policy(self._filter_policy, self._filters, filters)
|
|
192
192
|
top_k = top_k or self._top_k
|
|
193
193
|
|
|
194
|
-
distance = distance
|
|
195
|
-
certainty = certainty
|
|
194
|
+
distance = self._distance if distance is None else distance
|
|
195
|
+
certainty = self._certainty if certainty is None else certainty
|
|
196
196
|
if distance is not None and certainty is not None:
|
|
197
197
|
msg = f"Can't use 'distance' ({distance}) and 'certainty' ({certainty}) parameters together"
|
|
198
198
|
raise ValueError(msg)
|
|
@@ -33,6 +33,10 @@ def convert_filters(filters: dict[str, Any]) -> FilterReturn:
|
|
|
33
33
|
Note: The ``contains`` operator performs substring matching and is
|
|
34
34
|
**case-sensitive**. For case-insensitive matching, normalize the value
|
|
35
35
|
(e.g., lowercase) before building the filter.
|
|
36
|
+
|
|
37
|
+
Note: ``NOT`` is applied client-side by inverting its conditions (De Morgan), so it works
|
|
38
|
+
on any Weaviate version. The only exception is ``NOT`` over ``contains``, which has no
|
|
39
|
+
invertible counterpart and uses Weaviate's native ``NOT`` operator (Weaviate 1.33 or later).
|
|
36
40
|
"""
|
|
37
41
|
if not isinstance(filters, dict):
|
|
38
42
|
msg = "Filters must be a dictionary"
|
|
@@ -43,7 +47,13 @@ def convert_filters(filters: dict[str, Any]) -> FilterReturn:
|
|
|
43
47
|
return _parse_logical_condition(filters)
|
|
44
48
|
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
LOGICAL_OPERATORS = {
|
|
51
|
+
"AND": Filter.all_of,
|
|
52
|
+
"OR": Filter.any_of,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
COMPARISON_INVERSE = {
|
|
47
57
|
"==": "!=",
|
|
48
58
|
"!=": "==",
|
|
49
59
|
">": "<=",
|
|
@@ -52,38 +62,10 @@ OPERATOR_INVERSE = {
|
|
|
52
62
|
"<=": ">",
|
|
53
63
|
"in": "not in",
|
|
54
64
|
"not in": "in",
|
|
55
|
-
"AND": "OR",
|
|
56
|
-
"OR": "AND",
|
|
57
|
-
"NOT": "OR",
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _invert_condition(filters: dict[str, Any]) -> dict[str, Any]:
|
|
62
|
-
"""
|
|
63
|
-
Invert condition recursively.
|
|
64
|
-
|
|
65
|
-
Weaviate doesn't support NOT filters so we need to invert them ourselves.
|
|
66
|
-
"""
|
|
67
|
-
inverted_condition = filters.copy()
|
|
68
|
-
if "operator" not in filters:
|
|
69
|
-
# Let's not handle this stuff in here, we'll fail later on anyway.
|
|
70
|
-
return inverted_condition
|
|
71
|
-
inverted_condition["operator"] = OPERATOR_INVERSE[filters["operator"]]
|
|
72
|
-
if "conditions" in filters:
|
|
73
|
-
inverted_condition["conditions"] = []
|
|
74
|
-
for condition in filters["conditions"]:
|
|
75
|
-
inverted_condition["conditions"].append(_invert_condition(condition))
|
|
76
|
-
|
|
77
|
-
return inverted_condition
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
LOGICAL_OPERATORS = {
|
|
81
|
-
"AND": Filter.all_of,
|
|
82
|
-
"OR": Filter.any_of,
|
|
83
65
|
}
|
|
84
66
|
|
|
85
67
|
|
|
86
|
-
def
|
|
68
|
+
def _validate_logical_condition(condition: dict[str, Any]) -> None:
|
|
87
69
|
if "operator" not in condition:
|
|
88
70
|
msg = f"'operator' key missing in {condition}"
|
|
89
71
|
raise FilterError(msg)
|
|
@@ -91,23 +73,55 @@ def _parse_logical_condition(condition: dict[str, Any]) -> FilterReturn:
|
|
|
91
73
|
msg = f"'conditions' key missing in {condition}"
|
|
92
74
|
raise FilterError(msg)
|
|
93
75
|
|
|
76
|
+
|
|
77
|
+
def _parse_logical_condition(condition: dict[str, Any]) -> FilterReturn:
|
|
78
|
+
_validate_logical_condition(condition)
|
|
79
|
+
|
|
94
80
|
operator = condition["operator"]
|
|
95
81
|
if operator in ["AND", "OR"]:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
82
|
+
return LOGICAL_OPERATORS[operator](_parse_operands(condition["conditions"]))
|
|
83
|
+
elif operator == "NOT":
|
|
84
|
+
# A NOT node negates the conjunction of its conditions: NOT(a AND b) == NOT(a) OR NOT(b).
|
|
85
|
+
return Filter.any_of([_negate(c) for c in condition["conditions"]])
|
|
86
|
+
else:
|
|
87
|
+
msg = f"Unknown logical operator '{operator}'"
|
|
88
|
+
raise FilterError(msg)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _negate(condition: dict[str, Any]) -> FilterReturn:
|
|
92
|
+
"""
|
|
93
|
+
Build the negation of a condition by applying De Morgan's laws.
|
|
94
|
+
|
|
95
|
+
Negation is done client-side so that it works on Weaviate versions without a native NOT operator.
|
|
96
|
+
"""
|
|
97
|
+
if "field" in condition:
|
|
98
|
+
operator = condition.get("operator")
|
|
99
|
+
if operator in COMPARISON_INVERSE:
|
|
100
|
+
return _parse_comparison_condition({**condition, "operator": COMPARISON_INVERSE[operator]})
|
|
101
|
+
# Operators like `contains` have no invertible counterpart, so we fall back to Weaviate's
|
|
102
|
+
# native NOT, which requires Weaviate 1.33 or later.
|
|
103
|
+
# Unknown operators raise a FilterError while parsing the condition.
|
|
104
|
+
return Filter.not_(_parse_comparison_condition(condition))
|
|
105
|
+
|
|
106
|
+
_validate_logical_condition(condition)
|
|
107
|
+
operator = condition["operator"]
|
|
108
|
+
conditions = condition["conditions"]
|
|
109
|
+
if operator == "AND":
|
|
110
|
+
return Filter.any_of([_negate(c) for c in conditions])
|
|
111
|
+
elif operator == "OR":
|
|
112
|
+
return Filter.all_of([_negate(c) for c in conditions])
|
|
103
113
|
elif operator == "NOT":
|
|
104
|
-
|
|
105
|
-
return
|
|
114
|
+
# The two negations cancel out, leaving the conjunction of the conditions.
|
|
115
|
+
return Filter.all_of(_parse_operands(conditions))
|
|
106
116
|
else:
|
|
107
117
|
msg = f"Unknown logical operator '{operator}'"
|
|
108
118
|
raise FilterError(msg)
|
|
109
119
|
|
|
110
120
|
|
|
121
|
+
def _parse_operands(conditions: list[dict[str, Any]]) -> list[FilterReturn]:
|
|
122
|
+
return [_parse_comparison_condition(c) if "field" in c else _parse_logical_condition(c) for c in conditions]
|
|
123
|
+
|
|
124
|
+
|
|
111
125
|
def _handle_date(value: Any) -> str:
|
|
112
126
|
if isinstance(value, str):
|
|
113
127
|
try:
|
|
@@ -282,6 +296,10 @@ def _parse_comparison_condition(condition: dict[str, Any]) -> FilterReturn:
|
|
|
282
296
|
operator: str = condition["operator"]
|
|
283
297
|
value: Any = condition["value"]
|
|
284
298
|
|
|
299
|
+
if operator not in COMPARISON_OPERATORS:
|
|
300
|
+
msg = f"Unknown comparison operator '{operator}'. Valid operators are: {list(COMPARISON_OPERATORS.keys())}"
|
|
301
|
+
raise FilterError(msg)
|
|
302
|
+
|
|
285
303
|
return COMPARISON_OPERATORS[operator](field, value)
|
|
286
304
|
|
|
287
305
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
|
-
import datetime
|
|
6
|
+
import datetime as dt
|
|
7
|
+
import importlib.metadata
|
|
7
8
|
import json
|
|
8
9
|
from contextlib import suppress
|
|
9
10
|
from dataclasses import asdict
|
|
@@ -38,6 +39,27 @@ from .auth import AuthCredentials
|
|
|
38
39
|
|
|
39
40
|
logger = logging.getLogger(__name__)
|
|
40
41
|
|
|
42
|
+
# Weaviate tracks which framework integrations talk to it through this header, so that adoption of
|
|
43
|
+
# weaviate-haystack shows up in Weaviate's own telemetry. See https://github.com/weaviate/weaviate/pull/10535.
|
|
44
|
+
_INTEGRATION_HEADER = "X-Weaviate-Client-Integration"
|
|
45
|
+
_INTEGRATION_NAME = "haystack-python"
|
|
46
|
+
_PACKAGE_NAME = "weaviate-haystack"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _integration_header_value() -> str:
|
|
50
|
+
"""
|
|
51
|
+
Builds the value of the Weaviate client integration header.
|
|
52
|
+
|
|
53
|
+
:returns:
|
|
54
|
+
A `<name>/<version>` string. The version falls back to `unknown` when the package metadata is
|
|
55
|
+
unavailable, for example when running from a source checkout that was never installed.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
version = importlib.metadata.version(_PACKAGE_NAME)
|
|
59
|
+
except importlib.metadata.PackageNotFoundError:
|
|
60
|
+
version = "unknown"
|
|
61
|
+
return f"{_INTEGRATION_NAME}/{version}"
|
|
62
|
+
|
|
41
63
|
|
|
42
64
|
# This is the default collection properties for Weaviate.
|
|
43
65
|
# It's a list of properties that will be created on the collection.
|
|
@@ -144,6 +166,9 @@ class WeaviateDocumentStore:
|
|
|
144
166
|
```
|
|
145
167
|
{"X-OpenAI-Api-Key": "<THE-KEY>"}, {"X-HuggingFace-Api-Key": "<THE-KEY>"}
|
|
146
168
|
```
|
|
169
|
+
Every connection also carries `X-Weaviate-Client-Integration: haystack-python/<version>`, which
|
|
170
|
+
identifies this integration in Weaviate's telemetry. Supplying that header here, in any casing,
|
|
171
|
+
overrides it.
|
|
147
172
|
:param embedded_options:
|
|
148
173
|
If set, create an embedded Weaviate cluster inside the client. For a full list of options see
|
|
149
174
|
`weaviate.embedded.EmbeddedOptions`.
|
|
@@ -183,6 +208,26 @@ class WeaviateDocumentStore:
|
|
|
183
208
|
"properties", DOCUMENT_COLLECTION_PROPERTIES
|
|
184
209
|
)
|
|
185
210
|
|
|
211
|
+
def _connection_headers(self) -> dict[str, Any]:
|
|
212
|
+
"""
|
|
213
|
+
Builds the headers passed to the Weaviate client.
|
|
214
|
+
|
|
215
|
+
Merges the integration identification header into the user supplied `additional_headers`. A user
|
|
216
|
+
supplied value for the same header wins, so the header can be overridden or suppressed. The merged
|
|
217
|
+
headers are deliberately not stored on the instance: `to_dict()` must keep serializing only the
|
|
218
|
+
headers the user actually passed, so that a serialized pipeline does not bake in the version of
|
|
219
|
+
`weaviate-haystack` that happened to write it.
|
|
220
|
+
|
|
221
|
+
:returns:
|
|
222
|
+
The headers to pass to the Weaviate client.
|
|
223
|
+
"""
|
|
224
|
+
headers: dict[str, Any] = dict(self._additional_headers) if self._additional_headers else {}
|
|
225
|
+
# The comparison is case insensitive because gRPC metadata keys are case sensitive: a user supplied
|
|
226
|
+
# "x-weaviate-client-integration" would otherwise end up alongside our own spelling of the header.
|
|
227
|
+
if not any(key.lower() == _INTEGRATION_HEADER.lower() for key in headers):
|
|
228
|
+
headers[_INTEGRATION_HEADER] = _integration_header_value()
|
|
229
|
+
return headers
|
|
230
|
+
|
|
186
231
|
@property
|
|
187
232
|
def client(self) -> weaviate.WeaviateClient:
|
|
188
233
|
"""Return the synchronous Weaviate client, creating and connecting it if necessary."""
|
|
@@ -199,7 +244,7 @@ class WeaviateDocumentStore:
|
|
|
199
244
|
self._client = weaviate.connect_to_weaviate_cloud(
|
|
200
245
|
self._url,
|
|
201
246
|
auth_credentials=self._auth_client_secret.resolve_value(),
|
|
202
|
-
headers=self.
|
|
247
|
+
headers=self._connection_headers(),
|
|
203
248
|
additional_config=self._additional_config,
|
|
204
249
|
)
|
|
205
250
|
else:
|
|
@@ -216,7 +261,7 @@ class WeaviateDocumentStore:
|
|
|
216
261
|
),
|
|
217
262
|
auth_client_secret=self._auth_client_secret.resolve_value() if self._auth_client_secret else None,
|
|
218
263
|
additional_config=self._additional_config,
|
|
219
|
-
additional_headers=self.
|
|
264
|
+
additional_headers=self._connection_headers(),
|
|
220
265
|
embedded_options=self._embedded_options,
|
|
221
266
|
skip_init_checks=False,
|
|
222
267
|
)
|
|
@@ -244,7 +289,7 @@ class WeaviateDocumentStore:
|
|
|
244
289
|
self._async_client = weaviate.use_async_with_weaviate_cloud(
|
|
245
290
|
self._url,
|
|
246
291
|
auth_credentials=self._auth_client_secret.resolve_value(),
|
|
247
|
-
headers=self.
|
|
292
|
+
headers=self._connection_headers(),
|
|
248
293
|
additional_config=self._additional_config,
|
|
249
294
|
)
|
|
250
295
|
else:
|
|
@@ -261,7 +306,7 @@ class WeaviateDocumentStore:
|
|
|
261
306
|
),
|
|
262
307
|
auth_client_secret=self._auth_client_secret.resolve_value() if self._auth_client_secret else None,
|
|
263
308
|
additional_config=self._additional_config,
|
|
264
|
-
additional_headers=self.
|
|
309
|
+
additional_headers=self._connection_headers(),
|
|
265
310
|
embedded_options=self._embedded_options,
|
|
266
311
|
skip_init_checks=False,
|
|
267
312
|
)
|
|
@@ -831,7 +876,7 @@ class WeaviateDocumentStore:
|
|
|
831
876
|
document_data.pop("blob_mime_type", None)
|
|
832
877
|
|
|
833
878
|
for key, value in document_data.items():
|
|
834
|
-
if isinstance(value,
|
|
879
|
+
if isinstance(value, dt.datetime):
|
|
835
880
|
document_data[key] = value.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
836
881
|
|
|
837
882
|
if weaviate_meta := getattr(data, "metadata", None):
|
|
@@ -938,6 +983,9 @@ class WeaviateDocumentStore:
|
|
|
938
983
|
matching). For case-insensitive matching, normalize the value before
|
|
939
984
|
building the filter.
|
|
940
985
|
|
|
986
|
+
Note: ``NOT`` over the ``contains`` operator is translated to Weaviate's
|
|
987
|
+
native ``NOT``, which requires Weaviate 1.33 or later.
|
|
988
|
+
|
|
941
989
|
:param filters: The filters to apply to the document list.
|
|
942
990
|
:returns: A list of Documents that match the given filters.
|
|
943
991
|
"""
|
|
@@ -961,6 +1009,9 @@ class WeaviateDocumentStore:
|
|
|
961
1009
|
matching). For case-insensitive matching, normalize the value before
|
|
962
1010
|
building the filter.
|
|
963
1011
|
|
|
1012
|
+
Note: ``NOT`` over the ``contains`` operator is translated to Weaviate's
|
|
1013
|
+
native ``NOT``, which requires Weaviate 1.33 or later.
|
|
1014
|
+
|
|
964
1015
|
:param filters: The filters to apply to the document list.
|
|
965
1016
|
:returns: A list of Documents that match the given filters.
|
|
966
1017
|
"""
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
from unittest.mock import
|
|
5
|
+
from unittest.mock import create_autospec, patch
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
|
+
from haystack import Pipeline
|
|
8
9
|
from haystack.document_stores.types import FilterPolicy
|
|
9
10
|
|
|
10
11
|
from haystack_integrations.components.retrievers.weaviate import WeaviateBM25Retriever
|
|
@@ -12,7 +13,7 @@ from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def test_close():
|
|
15
|
-
mock_document_store =
|
|
16
|
+
mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
|
|
16
17
|
retriever = WeaviateBM25Retriever(document_store=mock_document_store)
|
|
17
18
|
|
|
18
19
|
retriever.close()
|
|
@@ -22,7 +23,7 @@ def test_close():
|
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def test_init_default():
|
|
25
|
-
mock_document_store =
|
|
26
|
+
mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
|
|
26
27
|
retriever = WeaviateBM25Retriever(document_store=mock_document_store)
|
|
27
28
|
assert retriever._document_store == mock_document_store
|
|
28
29
|
assert retriever._filters == {}
|
|
@@ -153,10 +154,24 @@ def test_from_dict_no_filter_policy(_mock_weaviate):
|
|
|
153
154
|
assert retriever._filter_policy == FilterPolicy.REPLACE
|
|
154
155
|
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
def test_run():
|
|
158
|
+
mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
|
|
158
159
|
retriever = WeaviateBM25Retriever(document_store=mock_document_store)
|
|
159
160
|
query = "some query"
|
|
160
161
|
filters = {"field": "content", "operator": "==", "value": "Some text"}
|
|
161
162
|
retriever.run(query=query, filters=filters, top_k=5)
|
|
162
163
|
mock_document_store._bm25_retrieval.assert_called_once_with(query=query, filters=filters, top_k=5)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_pipeline_serde():
|
|
167
|
+
"""A pipeline holding the retriever must survive a dumps/loads round trip, nested store included."""
|
|
168
|
+
document_store = WeaviateDocumentStore(url="http://localhost:8080")
|
|
169
|
+
pipeline = Pipeline()
|
|
170
|
+
pipeline.add_component("retriever", WeaviateBM25Retriever(document_store=document_store, top_k=3))
|
|
171
|
+
|
|
172
|
+
reloaded = Pipeline.loads(pipeline.dumps()).get_component("retriever")
|
|
173
|
+
|
|
174
|
+
assert isinstance(reloaded, WeaviateBM25Retriever)
|
|
175
|
+
assert isinstance(reloaded._document_store, WeaviateDocumentStore)
|
|
176
|
+
assert reloaded._document_store.to_dict() == document_store.to_dict()
|
|
177
|
+
assert reloaded._top_k == 3
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
-
from unittest.mock import
|
|
5
|
+
from unittest.mock import create_autospec
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
@@ -12,8 +12,7 @@ from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore
|
|
|
12
12
|
|
|
13
13
|
@pytest.mark.asyncio
|
|
14
14
|
async def test_close_async():
|
|
15
|
-
mock_document_store =
|
|
16
|
-
mock_document_store.close_async = AsyncMock()
|
|
15
|
+
mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
|
|
17
16
|
retriever = WeaviateBM25Retriever(document_store=mock_document_store)
|
|
18
17
|
|
|
19
18
|
await retriever.close_async()
|
|
@@ -24,8 +23,8 @@ async def test_close_async():
|
|
|
24
23
|
|
|
25
24
|
@pytest.mark.asyncio
|
|
26
25
|
async def test_run_async_calls_async_retrieval():
|
|
27
|
-
mock_document_store =
|
|
28
|
-
mock_document_store._bm25_retrieval_async =
|
|
26
|
+
mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
|
|
27
|
+
mock_document_store._bm25_retrieval_async.return_value = []
|
|
29
28
|
|
|
30
29
|
retriever = WeaviateBM25Retriever(document_store=mock_document_store)
|
|
31
30
|
|