weaviate-haystack 7.0.0__tar.gz → 7.2.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.0.0 → weaviate_haystack-7.2.0}/CHANGELOG.md +19 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/PKG-INFO +2 -2
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/docker-compose.yml +0 -1
- weaviate_haystack-7.2.0/pydoc/config_docusaurus.yml +17 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/pyproject.toml +2 -2
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/components/retrievers/weaviate/bm25_retriever.py +6 -2
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/document_stores/weaviate/_filters.py +21 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/document_stores/weaviate/document_store.py +398 -6
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_document_store.py +225 -53
- weaviate_haystack-7.2.0/tests/test_document_store_async.py +493 -0
- weaviate_haystack-7.0.0/pydoc/config_docusaurus.yml +0 -32
- weaviate_haystack-7.0.0/tests/test_document_store_async.py +0 -245
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/.gitignore +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/LICENSE.txt +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/README.md +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/components/retrievers/weaviate/__init__.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/components/retrievers/weaviate/embedding_retriever.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/components/retrievers/weaviate/hybrid_retriever.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/document_stores/py.typed +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/document_stores/weaviate/__init__.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/src/haystack_integrations/document_stores/weaviate/auth.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/__init__.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/conftest.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_auth.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_bm25_retriever.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_bm25_retriever_async.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_embedding_retriever.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_embedding_retriever_async.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_files/robot1.jpg +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_filters.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_hybrid_retriever.py +0 -0
- {weaviate_haystack-7.0.0 → weaviate_haystack-7.2.0}/tests/test_hybrid_retriever_async.py +0 -0
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/weaviate-v7.1.0] - 2026-01-29
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- Adding count with filtering operations to `WeaviateDocumentStore` (#2767)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [integrations/weaviate-v7.0.0] - 2026-01-13
|
|
11
|
+
|
|
12
|
+
### 🐛 Bug Fixes
|
|
13
|
+
|
|
14
|
+
- `WeaviateDocumentStore `_to_document()` and `to_data_object()` should be static methods (#2669)
|
|
15
|
+
|
|
16
|
+
### 🧹 Chores
|
|
17
|
+
|
|
18
|
+
- Make fmt command more forgiving (#2671)
|
|
19
|
+
- [**breaking**] Weaviate - drop Python 3.9 and use X|Y typing (#2733)
|
|
20
|
+
|
|
21
|
+
|
|
3
22
|
## [integrations/weaviate-v6.5.0] - 2026-01-08
|
|
4
23
|
|
|
5
24
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weaviate-haystack
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.2.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
|
|
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
19
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
|
-
Requires-Dist: haystack-ai>=2.
|
|
21
|
+
Requires-Dist: haystack-ai>=2.24.0
|
|
22
22
|
Requires-Dist: python-dateutil
|
|
23
23
|
Requires-Dist: weaviate-client>=4.9
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
loaders:
|
|
2
|
+
- modules:
|
|
3
|
+
- haystack_integrations.document_stores.weaviate.auth
|
|
4
|
+
- haystack_integrations.document_stores.weaviate.document_store
|
|
5
|
+
- haystack_integrations.components.retrievers.weaviate.bm25_retriever
|
|
6
|
+
- haystack_integrations.components.retrievers.weaviate.embedding_retriever
|
|
7
|
+
- haystack_integrations.components.retrievers.weaviate.hybrid_retriever
|
|
8
|
+
search_path: [../src]
|
|
9
|
+
processors:
|
|
10
|
+
- type: filter
|
|
11
|
+
documented_only: true
|
|
12
|
+
skip_empty_modules: true
|
|
13
|
+
renderer:
|
|
14
|
+
description: Weaviate integration for Haystack
|
|
15
|
+
id: integrations-weaviate
|
|
16
|
+
filename: weaviate.md
|
|
17
|
+
title: Weaviate
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"haystack-ai>=2.
|
|
26
|
+
"haystack-ai>=2.24.0",
|
|
27
27
|
"weaviate-client>=4.9",
|
|
28
28
|
"python-dateutil",
|
|
29
29
|
]
|
|
@@ -49,7 +49,7 @@ installer = "uv"
|
|
|
49
49
|
dependencies = ["haystack-pydoc-tools", "ruff"]
|
|
50
50
|
|
|
51
51
|
[tool.hatch.envs.default.scripts]
|
|
52
|
-
docs = ["pydoc
|
|
52
|
+
docs = ["haystack-pydoc pydoc/config_docusaurus.yml"]
|
|
53
53
|
fmt = "ruff check --fix {args}; ruff format {args}"
|
|
54
54
|
fmt-check = "ruff check {args} && ruff format --check {args}"
|
|
55
55
|
|
|
@@ -18,8 +18,12 @@ class WeaviateBM25Retriever:
|
|
|
18
18
|
|
|
19
19
|
Example usage:
|
|
20
20
|
```python
|
|
21
|
-
from haystack_integrations.document_stores.weaviate.document_store import
|
|
22
|
-
|
|
21
|
+
from haystack_integrations.document_stores.weaviate.document_store import (
|
|
22
|
+
WeaviateDocumentStore,
|
|
23
|
+
)
|
|
24
|
+
from haystack_integrations.components.retrievers.weaviate.bm25_retriever import (
|
|
25
|
+
WeaviateBM25Retriever,
|
|
26
|
+
)
|
|
23
27
|
|
|
24
28
|
document_store = WeaviateDocumentStore(url="http://localhost:8080")
|
|
25
29
|
retriever = WeaviateBM25Retriever(document_store=document_store)
|
|
@@ -26,6 +26,13 @@ def validate_filters(filters: dict[str, Any] | None) -> None:
|
|
|
26
26
|
def convert_filters(filters: dict[str, Any]) -> FilterReturn:
|
|
27
27
|
"""
|
|
28
28
|
Convert filters from Haystack format to Weaviate format.
|
|
29
|
+
|
|
30
|
+
Supported comparison operators: ``==``, ``!=``, ``>``, ``>=``, ``<``, ``<=``,
|
|
31
|
+
``in``, ``not in``, ``contains``.
|
|
32
|
+
|
|
33
|
+
Note: The ``contains`` operator performs substring matching and is
|
|
34
|
+
**case-sensitive**. For case-insensitive matching, normalize the value
|
|
35
|
+
(e.g., lowercase) before building the filter.
|
|
29
36
|
"""
|
|
30
37
|
if not isinstance(filters, dict):
|
|
31
38
|
msg = "Filters must be a dictionary"
|
|
@@ -228,6 +235,19 @@ def _not_in(field: str, value: Any) -> FilterReturn:
|
|
|
228
235
|
return Filter.all_of(operands)
|
|
229
236
|
|
|
230
237
|
|
|
238
|
+
def _contains(field: str, value: Any) -> FilterReturn:
|
|
239
|
+
"""
|
|
240
|
+
Creates a filter for substring matching using Weaviate's 'like' operator.
|
|
241
|
+
|
|
242
|
+
The matching is case-sensitive. For case-insensitive matching, consider
|
|
243
|
+
normalizing the value before passing it to this function.
|
|
244
|
+
"""
|
|
245
|
+
if not isinstance(value, str):
|
|
246
|
+
msg = "Filter value must be a string when using 'contains' comparator"
|
|
247
|
+
raise FilterError(msg)
|
|
248
|
+
return weaviate.classes.query.Filter.by_property(field).like(f"*{value}*")
|
|
249
|
+
|
|
250
|
+
|
|
231
251
|
COMPARISON_OPERATORS = {
|
|
232
252
|
"==": _equal,
|
|
233
253
|
"!=": _not_equal,
|
|
@@ -237,6 +257,7 @@ COMPARISON_OPERATORS = {
|
|
|
237
257
|
"<=": _less_than_equal,
|
|
238
258
|
"in": _in,
|
|
239
259
|
"not in": _not_in,
|
|
260
|
+
"contains": _contains,
|
|
240
261
|
}
|
|
241
262
|
|
|
242
263
|
|
|
@@ -15,6 +15,13 @@ from haystack.document_stores.errors import DocumentStoreError, DuplicateDocumen
|
|
|
15
15
|
from haystack.document_stores.types.policy import DuplicatePolicy
|
|
16
16
|
|
|
17
17
|
import weaviate
|
|
18
|
+
from weaviate.collections.classes.aggregate import (
|
|
19
|
+
GroupByAggregate,
|
|
20
|
+
Metrics,
|
|
21
|
+
_MetricsDate,
|
|
22
|
+
_MetricsInteger,
|
|
23
|
+
_MetricsNumber,
|
|
24
|
+
)
|
|
18
25
|
from weaviate.collections.classes.data import DataObject
|
|
19
26
|
from weaviate.config import AdditionalConfig
|
|
20
27
|
from weaviate.embedded import EmbeddedOptions
|
|
@@ -65,7 +72,9 @@ class WeaviateDocumentStore:
|
|
|
65
72
|
```python
|
|
66
73
|
import os
|
|
67
74
|
from haystack_integrations.document_stores.weaviate.auth import AuthApiKey
|
|
68
|
-
from haystack_integrations.document_stores.weaviate.document_store import
|
|
75
|
+
from haystack_integrations.document_stores.weaviate.document_store import (
|
|
76
|
+
WeaviateDocumentStore,
|
|
77
|
+
)
|
|
69
78
|
|
|
70
79
|
os.environ["WEAVIATE_API_KEY"] = "MY_API_KEY"
|
|
71
80
|
|
|
@@ -77,7 +86,9 @@ class WeaviateDocumentStore:
|
|
|
77
86
|
|
|
78
87
|
Usage example with self-hosted Weaviate:
|
|
79
88
|
```python
|
|
80
|
-
from haystack_integrations.document_stores.weaviate.document_store import
|
|
89
|
+
from haystack_integrations.document_stores.weaviate.document_store import (
|
|
90
|
+
WeaviateDocumentStore,
|
|
91
|
+
)
|
|
81
92
|
|
|
82
93
|
document_store = WeaviateDocumentStore(url="http://localhost:8080")
|
|
83
94
|
```
|
|
@@ -206,8 +217,6 @@ class WeaviateDocumentStore:
|
|
|
206
217
|
|
|
207
218
|
self._client.connect()
|
|
208
219
|
|
|
209
|
-
# Test connection, it will raise an exception if it fails.
|
|
210
|
-
self._client.collections.list_all(simple=True)
|
|
211
220
|
if not self._client.collections.exists(self._collection_settings["class"]):
|
|
212
221
|
self._client.collections.create_from_dict(self._collection_settings)
|
|
213
222
|
|
|
@@ -251,8 +260,7 @@ class WeaviateDocumentStore:
|
|
|
251
260
|
)
|
|
252
261
|
|
|
253
262
|
await self._async_client.connect()
|
|
254
|
-
|
|
255
|
-
await self._async_client.collections.list_all(simple=True)
|
|
263
|
+
|
|
256
264
|
if not await self._async_client.collections.exists(self._collection_settings["class"]):
|
|
257
265
|
await self._async_client.collections.create_from_dict(self._collection_settings)
|
|
258
266
|
|
|
@@ -276,6 +284,24 @@ class WeaviateDocumentStore:
|
|
|
276
284
|
self._async_collection = async_client.collections.get(self._collection_settings["class"])
|
|
277
285
|
return self._async_collection
|
|
278
286
|
|
|
287
|
+
def close(self) -> None:
|
|
288
|
+
"""
|
|
289
|
+
Close the synchronous Weaviate client connection.
|
|
290
|
+
"""
|
|
291
|
+
if self._client:
|
|
292
|
+
self._client.close()
|
|
293
|
+
self._client = None
|
|
294
|
+
self._collection = None
|
|
295
|
+
|
|
296
|
+
async def close_async(self) -> None:
|
|
297
|
+
"""
|
|
298
|
+
Close the asynchronous Weaviate client connection.
|
|
299
|
+
"""
|
|
300
|
+
if self._async_client:
|
|
301
|
+
await self._async_client.close()
|
|
302
|
+
self._async_client = None
|
|
303
|
+
self._async_collection = None
|
|
304
|
+
|
|
279
305
|
def to_dict(self) -> dict[str, Any]:
|
|
280
306
|
"""
|
|
281
307
|
Serializes the component to a dictionary.
|
|
@@ -326,6 +352,368 @@ class WeaviateDocumentStore:
|
|
|
326
352
|
total = self.collection.aggregate.over_all(total_count=True).total_count
|
|
327
353
|
return total if total else 0
|
|
328
354
|
|
|
355
|
+
def count_documents_by_filter(self, filters: dict[str, Any]) -> int:
|
|
356
|
+
"""
|
|
357
|
+
Returns the number of documents that match the provided filters.
|
|
358
|
+
|
|
359
|
+
:param filters: The filters to apply to count documents.
|
|
360
|
+
For filter syntax, see
|
|
361
|
+
[Haystack metadata filtering](https://docs.haystack.deepset.ai/docs/metadata-filtering).
|
|
362
|
+
:returns: The number of documents that match the filters.
|
|
363
|
+
"""
|
|
364
|
+
validate_filters(filters)
|
|
365
|
+
weaviate_filter = convert_filters(filters)
|
|
366
|
+
total = self.collection.aggregate.over_all(filters=weaviate_filter, total_count=True).total_count
|
|
367
|
+
return total if total else 0
|
|
368
|
+
|
|
369
|
+
async def count_documents_by_filter_async(self, filters: dict[str, Any]) -> int:
|
|
370
|
+
"""
|
|
371
|
+
Asynchronously returns the number of documents that match the provided filters.
|
|
372
|
+
|
|
373
|
+
:param filters: The filters to apply to count documents.
|
|
374
|
+
For filter syntax, see
|
|
375
|
+
[Haystack metadata filtering](https://docs.haystack.deepset.ai/docs/metadata-filtering).
|
|
376
|
+
:returns: The number of documents that match the filters.
|
|
377
|
+
"""
|
|
378
|
+
validate_filters(filters)
|
|
379
|
+
collection = await self.async_collection
|
|
380
|
+
weaviate_filter = convert_filters(filters)
|
|
381
|
+
result = await collection.aggregate.over_all(filters=weaviate_filter, total_count=True)
|
|
382
|
+
return result.total_count if result.total_count else 0
|
|
383
|
+
|
|
384
|
+
def get_metadata_fields_info(self) -> dict[str, dict[str, str]]:
|
|
385
|
+
"""
|
|
386
|
+
Returns metadata field names and their types, excluding special fields.
|
|
387
|
+
|
|
388
|
+
Special fields (content, blob_data, blob_mime_type, _original_id, score) are excluded
|
|
389
|
+
as they are not user metadata fields.
|
|
390
|
+
|
|
391
|
+
:returns: A dictionary where keys are field names and values are dictionaries
|
|
392
|
+
containing type information, e.g.:
|
|
393
|
+
```python
|
|
394
|
+
{
|
|
395
|
+
'number': {'type': 'int'},
|
|
396
|
+
'date': {'type': 'date'},
|
|
397
|
+
'category': {'type': 'text'},
|
|
398
|
+
'status': {'type': 'text'}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
"""
|
|
402
|
+
config = self.collection.config.get()
|
|
403
|
+
special_fields = {prop["name"] for prop in DOCUMENT_COLLECTION_PROPERTIES}
|
|
404
|
+
fields_info = {}
|
|
405
|
+
for prop in config.properties:
|
|
406
|
+
if prop.name not in special_fields:
|
|
407
|
+
data_type = str(prop.data_type.value) if hasattr(prop.data_type, "value") else str(prop.data_type)
|
|
408
|
+
fields_info[prop.name] = {"type": data_type}
|
|
409
|
+
return fields_info
|
|
410
|
+
|
|
411
|
+
async def get_metadata_fields_info_async(self) -> dict[str, dict[str, str]]:
|
|
412
|
+
"""
|
|
413
|
+
Asynchronously returns metadata field names and their types, excluding special fields.
|
|
414
|
+
|
|
415
|
+
Special fields (content, blob_data, blob_mime_type, _original_id, score) are excluded
|
|
416
|
+
as they are not user metadata fields.
|
|
417
|
+
|
|
418
|
+
:returns: A dictionary where keys are field names and values are dictionaries
|
|
419
|
+
containing type information, e.g.:
|
|
420
|
+
```python
|
|
421
|
+
{
|
|
422
|
+
'number': {'type': 'int'},
|
|
423
|
+
'date': {'type': 'date'},
|
|
424
|
+
'category': {'type': 'text'},
|
|
425
|
+
'status': {'type': 'text'}
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
"""
|
|
429
|
+
collection = await self.async_collection
|
|
430
|
+
config = await collection.config.get()
|
|
431
|
+
special_fields = {prop["name"] for prop in DOCUMENT_COLLECTION_PROPERTIES}
|
|
432
|
+
fields_info = {}
|
|
433
|
+
for prop in config.properties:
|
|
434
|
+
if prop.name not in special_fields:
|
|
435
|
+
data_type = str(prop.data_type.value) if hasattr(prop.data_type, "value") else str(prop.data_type)
|
|
436
|
+
fields_info[prop.name] = {"type": data_type}
|
|
437
|
+
return fields_info
|
|
438
|
+
|
|
439
|
+
@staticmethod
|
|
440
|
+
def _normalize_metadata_field_name(metadata_field: str) -> str:
|
|
441
|
+
"""
|
|
442
|
+
Removes 'meta.' prefix from field name if present.
|
|
443
|
+
|
|
444
|
+
:param metadata_field: The field name, possibly prefixed with 'meta.'.
|
|
445
|
+
:returns: The field name without the 'meta.' prefix.
|
|
446
|
+
"""
|
|
447
|
+
return metadata_field[5:] if metadata_field.startswith("meta.") else metadata_field
|
|
448
|
+
|
|
449
|
+
def get_metadata_field_min_max(self, metadata_field: str) -> dict[str, Any]:
|
|
450
|
+
"""
|
|
451
|
+
Returns the minimum and maximum values for a numeric or date metadata field.
|
|
452
|
+
|
|
453
|
+
:param metadata_field: The metadata field name to get min/max for.
|
|
454
|
+
Can be prefixed with 'meta.' (e.g., 'meta.year' or 'year').
|
|
455
|
+
:returns: A dictionary with 'min' and 'max' keys containing the respective values.
|
|
456
|
+
:raises ValueError: If the field is not found or doesn't support min/max operations.
|
|
457
|
+
"""
|
|
458
|
+
field_name = self._normalize_metadata_field_name(metadata_field)
|
|
459
|
+
|
|
460
|
+
# Get field type from schema
|
|
461
|
+
config = self.collection.config.get()
|
|
462
|
+
field_type = None
|
|
463
|
+
for prop in config.properties:
|
|
464
|
+
if prop.name == field_name:
|
|
465
|
+
field_type = prop.data_type
|
|
466
|
+
break
|
|
467
|
+
|
|
468
|
+
if field_type is None:
|
|
469
|
+
msg = f"Field '{field_name}' not found in collection schema"
|
|
470
|
+
raise ValueError(msg)
|
|
471
|
+
|
|
472
|
+
data_type_str = str(field_type.value) if hasattr(field_type, "value") else str(field_type)
|
|
473
|
+
|
|
474
|
+
# Build metrics based on type.
|
|
475
|
+
# Explicit type annotation needed for mypy compatibility with Python 3.10,
|
|
476
|
+
# which infers the type from the first assignment and fails on reassignment.
|
|
477
|
+
metrics: _MetricsInteger | _MetricsNumber | _MetricsDate
|
|
478
|
+
if data_type_str.lower() == "int":
|
|
479
|
+
metrics = Metrics(field_name).integer(minimum=True, maximum=True)
|
|
480
|
+
elif data_type_str.lower() == "number":
|
|
481
|
+
metrics = Metrics(field_name).number(minimum=True, maximum=True)
|
|
482
|
+
elif data_type_str.lower() == "date":
|
|
483
|
+
metrics = Metrics(field_name).date_(minimum=True, maximum=True)
|
|
484
|
+
else:
|
|
485
|
+
msg = f"Field type '{data_type_str}' doesn't support min/max aggregation"
|
|
486
|
+
raise ValueError(msg)
|
|
487
|
+
|
|
488
|
+
result = self.collection.aggregate.over_all(return_metrics=metrics)
|
|
489
|
+
field_metrics = result.properties.get(field_name)
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
"min": getattr(field_metrics, "minimum", None) if field_metrics else None,
|
|
493
|
+
"max": getattr(field_metrics, "maximum", None) if field_metrics else None,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async def get_metadata_field_min_max_async(self, metadata_field: str) -> dict[str, Any]:
|
|
497
|
+
"""
|
|
498
|
+
Asynchronously returns the minimum and maximum values for a numeric or date metadata field.
|
|
499
|
+
|
|
500
|
+
:param metadata_field: The metadata field name to get min/max for.
|
|
501
|
+
Can be prefixed with 'meta.' (e.g., 'meta.year' or 'year').
|
|
502
|
+
:returns: A dictionary with 'min' and 'max' keys containing the respective values.
|
|
503
|
+
:raises ValueError: If the field is not found or doesn't support min/max operations.
|
|
504
|
+
"""
|
|
505
|
+
field_name = self._normalize_metadata_field_name(metadata_field)
|
|
506
|
+
|
|
507
|
+
# Get field type from schema
|
|
508
|
+
collection = await self.async_collection
|
|
509
|
+
config = await collection.config.get()
|
|
510
|
+
field_type = None
|
|
511
|
+
for prop in config.properties:
|
|
512
|
+
if prop.name == field_name:
|
|
513
|
+
field_type = prop.data_type
|
|
514
|
+
break
|
|
515
|
+
|
|
516
|
+
if field_type is None:
|
|
517
|
+
msg = f"Field '{field_name}' not found in collection schema"
|
|
518
|
+
raise ValueError(msg)
|
|
519
|
+
|
|
520
|
+
data_type_str = str(field_type.value) if hasattr(field_type, "value") else str(field_type)
|
|
521
|
+
|
|
522
|
+
# Build metrics based on type.
|
|
523
|
+
# Explicit type annotation needed for mypy compatibility with Python 3.10,
|
|
524
|
+
# which infers the type from the first assignment and fails on reassignment.
|
|
525
|
+
metrics: _MetricsInteger | _MetricsNumber | _MetricsDate
|
|
526
|
+
if data_type_str.lower() == "int":
|
|
527
|
+
metrics = Metrics(field_name).integer(minimum=True, maximum=True)
|
|
528
|
+
elif data_type_str.lower() == "number":
|
|
529
|
+
metrics = Metrics(field_name).number(minimum=True, maximum=True)
|
|
530
|
+
elif data_type_str.lower() == "date":
|
|
531
|
+
metrics = Metrics(field_name).date_(minimum=True, maximum=True)
|
|
532
|
+
else:
|
|
533
|
+
msg = f"Field type '{data_type_str}' doesn't support min/max aggregation"
|
|
534
|
+
raise ValueError(msg)
|
|
535
|
+
|
|
536
|
+
result = await collection.aggregate.over_all(return_metrics=metrics)
|
|
537
|
+
field_metrics = result.properties.get(field_name)
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
"min": getattr(field_metrics, "minimum", None) if field_metrics else None,
|
|
541
|
+
"max": getattr(field_metrics, "maximum", None) if field_metrics else None,
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
def count_unique_metadata_by_filter(self, filters: dict[str, Any], metadata_fields: list[str]) -> dict[str, int]:
|
|
545
|
+
"""
|
|
546
|
+
Returns the count of unique values for each specified metadata field.
|
|
547
|
+
|
|
548
|
+
:param filters: The filters to apply when counting unique values.
|
|
549
|
+
For filter syntax, see
|
|
550
|
+
[Haystack metadata filtering](https://docs.haystack.deepset.ai/docs/metadata-filtering).
|
|
551
|
+
:param metadata_fields: List of metadata field names to count unique values for.
|
|
552
|
+
Field names can be prefixed with 'meta.' (e.g., 'meta.category' or 'category').
|
|
553
|
+
:returns: A dictionary mapping field names to counts of unique values.
|
|
554
|
+
:raises ValueError: If any of the requested fields don't exist in the collection schema.
|
|
555
|
+
"""
|
|
556
|
+
validate_filters(filters)
|
|
557
|
+
weaviate_filter = convert_filters(filters)
|
|
558
|
+
|
|
559
|
+
normalized_fields = [self._normalize_metadata_field_name(f) for f in metadata_fields]
|
|
560
|
+
|
|
561
|
+
# Validate that all requested fields exist in the schema
|
|
562
|
+
config = self.collection.config.get()
|
|
563
|
+
schema_fields = {prop.name for prop in config.properties}
|
|
564
|
+
missing_fields = [f for f in normalized_fields if f not in schema_fields]
|
|
565
|
+
if missing_fields:
|
|
566
|
+
msg = f"Fields not found in collection schema: {missing_fields}"
|
|
567
|
+
raise ValueError(msg)
|
|
568
|
+
|
|
569
|
+
result = {}
|
|
570
|
+
for field in normalized_fields:
|
|
571
|
+
agg_result = self.collection.aggregate.over_all(
|
|
572
|
+
filters=weaviate_filter, group_by=GroupByAggregate(prop=field)
|
|
573
|
+
)
|
|
574
|
+
result[field] = len(agg_result.groups) if agg_result.groups else 0
|
|
575
|
+
|
|
576
|
+
return result
|
|
577
|
+
|
|
578
|
+
async def count_unique_metadata_by_filter_async(
|
|
579
|
+
self, filters: dict[str, Any], metadata_fields: list[str]
|
|
580
|
+
) -> dict[str, int]:
|
|
581
|
+
"""
|
|
582
|
+
Asynchronously returns the count of unique values for each specified metadata field.
|
|
583
|
+
|
|
584
|
+
:param filters: The filters to apply when counting unique values.
|
|
585
|
+
For filter syntax, see
|
|
586
|
+
[Haystack metadata filtering](https://docs.haystack.deepset.ai/docs/metadata-filtering).
|
|
587
|
+
:param metadata_fields: List of metadata field names to count unique values for.
|
|
588
|
+
Field names can be prefixed with 'meta.' (e.g., 'meta.category' or 'category').
|
|
589
|
+
:returns: A dictionary mapping field names to counts of unique values.
|
|
590
|
+
:raises ValueError: If any of the requested fields don't exist in the collection schema.
|
|
591
|
+
"""
|
|
592
|
+
validate_filters(filters)
|
|
593
|
+
collection = await self.async_collection
|
|
594
|
+
weaviate_filter = convert_filters(filters)
|
|
595
|
+
|
|
596
|
+
normalized_fields = [self._normalize_metadata_field_name(f) for f in metadata_fields]
|
|
597
|
+
|
|
598
|
+
# Validate that all requested fields exist in the schema
|
|
599
|
+
config = await collection.config.get()
|
|
600
|
+
schema_fields = {prop.name for prop in config.properties}
|
|
601
|
+
missing_fields = [f for f in normalized_fields if f not in schema_fields]
|
|
602
|
+
if missing_fields:
|
|
603
|
+
msg = f"Fields not found in collection schema: {missing_fields}"
|
|
604
|
+
raise ValueError(msg)
|
|
605
|
+
|
|
606
|
+
result = {}
|
|
607
|
+
for field in normalized_fields:
|
|
608
|
+
agg_result = await collection.aggregate.over_all(
|
|
609
|
+
filters=weaviate_filter, group_by=GroupByAggregate(prop=field)
|
|
610
|
+
)
|
|
611
|
+
result[field] = len(agg_result.groups) if agg_result.groups else 0
|
|
612
|
+
|
|
613
|
+
return result
|
|
614
|
+
|
|
615
|
+
def get_metadata_field_unique_values(
|
|
616
|
+
self,
|
|
617
|
+
metadata_field: str,
|
|
618
|
+
search_term: str | None = None,
|
|
619
|
+
from_: int = 0,
|
|
620
|
+
size: int = 10000,
|
|
621
|
+
) -> tuple[list[str], int]:
|
|
622
|
+
"""
|
|
623
|
+
Returns unique values for a metadata field with pagination support.
|
|
624
|
+
|
|
625
|
+
:param metadata_field: The metadata field name to get unique values for.
|
|
626
|
+
Can be prefixed with 'meta.' (e.g., 'meta.category' or 'category').
|
|
627
|
+
:param search_term: Optional term to filter documents by content before
|
|
628
|
+
extracting unique values. If provided, only documents whose content
|
|
629
|
+
contains this term will be considered.
|
|
630
|
+
Note: Uses substring matching (case-sensitive, no stemming).
|
|
631
|
+
:param from_: The starting offset for pagination (0-indexed). Defaults to 0.
|
|
632
|
+
:param size: The maximum number of unique values to return. Defaults to 10000.
|
|
633
|
+
:returns: A tuple of (list of unique values, total count of unique values).
|
|
634
|
+
:raises ValueError: If the field is not found in the collection schema.
|
|
635
|
+
"""
|
|
636
|
+
field_name = self._normalize_metadata_field_name(metadata_field)
|
|
637
|
+
|
|
638
|
+
config = self.collection.config.get()
|
|
639
|
+
schema_fields = {prop.name for prop in config.properties}
|
|
640
|
+
if field_name not in schema_fields:
|
|
641
|
+
msg = f"Field '{field_name}' not found in collection schema"
|
|
642
|
+
raise ValueError(msg)
|
|
643
|
+
|
|
644
|
+
weaviate_filter = None
|
|
645
|
+
if search_term:
|
|
646
|
+
weaviate_filter = convert_filters({"field": "content", "operator": "contains", "value": search_term})
|
|
647
|
+
|
|
648
|
+
# Weaviate's GroupByAggregate has a default limit of 100 groups.
|
|
649
|
+
# We use the document count as the limit to ensure all unique values are retrieved,
|
|
650
|
+
# since the number of unique values cannot exceed the number of documents.
|
|
651
|
+
doc_count = self.collection.aggregate.over_all(total_count=True).total_count or 0
|
|
652
|
+
|
|
653
|
+
agg_result = self.collection.aggregate.over_all(
|
|
654
|
+
filters=weaviate_filter, group_by=GroupByAggregate(prop=field_name, limit=doc_count)
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
all_values = [str(g.grouped_by.value) for g in agg_result.groups] if agg_result.groups else []
|
|
658
|
+
all_values.sort() # Sort for consistent pagination
|
|
659
|
+
total_count = len(all_values)
|
|
660
|
+
|
|
661
|
+
paginated_values = all_values[from_ : from_ + size]
|
|
662
|
+
|
|
663
|
+
return paginated_values, total_count
|
|
664
|
+
|
|
665
|
+
async def get_metadata_field_unique_values_async(
|
|
666
|
+
self,
|
|
667
|
+
metadata_field: str,
|
|
668
|
+
search_term: str | None = None,
|
|
669
|
+
from_: int = 0,
|
|
670
|
+
size: int = 10000,
|
|
671
|
+
) -> tuple[list[str], int]:
|
|
672
|
+
"""
|
|
673
|
+
Asynchronously returns unique values for a metadata field with pagination support.
|
|
674
|
+
|
|
675
|
+
:param metadata_field: The metadata field name to get unique values for.
|
|
676
|
+
Can be prefixed with 'meta.' (e.g., 'meta.category' or 'category').
|
|
677
|
+
:param search_term: Optional term to filter documents by content before
|
|
678
|
+
extracting unique values. If provided, only documents whose content
|
|
679
|
+
contains this term will be considered.
|
|
680
|
+
Note: Uses substring matching (case-sensitive, no stemming).
|
|
681
|
+
:param from_: The starting offset for pagination (0-indexed). Defaults to 0.
|
|
682
|
+
:param size: The maximum number of unique values to return. Defaults to 10000.
|
|
683
|
+
:returns: A tuple of (list of unique values, total count of unique values).
|
|
684
|
+
:raises ValueError: If the field is not found in the collection schema.
|
|
685
|
+
"""
|
|
686
|
+
field_name = self._normalize_metadata_field_name(metadata_field)
|
|
687
|
+
|
|
688
|
+
collection = await self.async_collection
|
|
689
|
+
config = await collection.config.get()
|
|
690
|
+
schema_fields = {prop.name for prop in config.properties}
|
|
691
|
+
if field_name not in schema_fields:
|
|
692
|
+
msg = f"Field '{field_name}' not found in collection schema"
|
|
693
|
+
raise ValueError(msg)
|
|
694
|
+
|
|
695
|
+
weaviate_filter = None
|
|
696
|
+
if search_term:
|
|
697
|
+
weaviate_filter = convert_filters({"field": "content", "operator": "contains", "value": search_term})
|
|
698
|
+
|
|
699
|
+
# Weaviate's GroupByAggregate has a default limit of 100 groups.
|
|
700
|
+
# We use the document count as the limit to ensure all unique values are retrieved,
|
|
701
|
+
# since the number of unique values cannot exceed the number of documents.
|
|
702
|
+
doc_count_result = await collection.aggregate.over_all(total_count=True)
|
|
703
|
+
doc_count = doc_count_result.total_count or 0
|
|
704
|
+
|
|
705
|
+
agg_result = await collection.aggregate.over_all(
|
|
706
|
+
filters=weaviate_filter, group_by=GroupByAggregate(prop=field_name, limit=doc_count)
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
all_values = [str(g.grouped_by.value) for g in agg_result.groups] if agg_result.groups else []
|
|
710
|
+
all_values.sort() # Sort for consistent pagination
|
|
711
|
+
total_count = len(all_values)
|
|
712
|
+
|
|
713
|
+
paginated_values = all_values[from_ : from_ + size]
|
|
714
|
+
|
|
715
|
+
return paginated_values, total_count
|
|
716
|
+
|
|
329
717
|
@staticmethod
|
|
330
718
|
def _to_data_object(document: Document) -> dict[str, Any]:
|
|
331
719
|
"""
|
|
@@ -452,6 +840,10 @@ class WeaviateDocumentStore:
|
|
|
452
840
|
For a detailed specification of the filters, refer to the
|
|
453
841
|
DocumentStore.filter_documents() protocol documentation.
|
|
454
842
|
|
|
843
|
+
Note: The ``contains`` filter operator is case-sensitive (substring
|
|
844
|
+
matching). For case-insensitive matching, normalize the value before
|
|
845
|
+
building the filter.
|
|
846
|
+
|
|
455
847
|
:param filters: The filters to apply to the document list.
|
|
456
848
|
:returns: A list of Documents that match the given filters.
|
|
457
849
|
"""
|