weaviate-haystack 7.2.0__tar.gz → 7.4.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.2.0 → weaviate_haystack-7.4.0}/CHANGELOG.md +23 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/PKG-INFO +2 -2
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/docker-compose.yml +1 -1
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/pyproject.toml +1 -1
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/components/retrievers/weaviate/hybrid_retriever.py +3 -3
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/document_stores/weaviate/document_store.py +284 -46
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_document_store.py +20 -17
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_document_store_async.py +132 -34
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_hybrid_retriever.py +16 -11
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/.gitignore +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/LICENSE.txt +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/README.md +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/pydoc/config_docusaurus.yml +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/components/retrievers/weaviate/__init__.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/components/retrievers/weaviate/bm25_retriever.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/components/retrievers/weaviate/embedding_retriever.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/document_stores/py.typed +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/document_stores/weaviate/__init__.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/document_stores/weaviate/_filters.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/src/haystack_integrations/document_stores/weaviate/auth.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/__init__.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/conftest.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_auth.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_bm25_retriever.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_bm25_retriever_async.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_embedding_retriever.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_embedding_retriever_async.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_files/robot1.jpg +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_filters.py +0 -0
- {weaviate_haystack-7.2.0 → weaviate_haystack-7.4.0}/tests/test_hybrid_retriever_async.py +0 -0
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/weaviate-v7.3.0] - 2026-03-11
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
|
|
7
|
+
- Add missing async methods for `WeaviateDocumentStore` (#2929)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [integrations/weaviate-v7.2.0] - 2026-03-06
|
|
11
|
+
|
|
12
|
+
### 🐛 Bug Fixes
|
|
13
|
+
|
|
14
|
+
- Remove unnecessary connection test and add `close`/`close_async` methods to `WeaviateDocumentStore` (#2891)
|
|
15
|
+
|
|
16
|
+
### 📚 Documentation
|
|
17
|
+
|
|
18
|
+
- Simplify pydoc configs (#2855)
|
|
19
|
+
|
|
20
|
+
### 🧪 Testing
|
|
21
|
+
|
|
22
|
+
- Replacing each `DocumentStore` specific tests and used the generalised ones from `haystack.testing.document_store` (#2812)
|
|
23
|
+
- Fix Weaviate tests to include grpc_config (#2871)
|
|
24
|
+
|
|
25
|
+
|
|
3
26
|
## [integrations/weaviate-v7.1.0] - 2026-01-29
|
|
4
27
|
|
|
5
28
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weaviate-haystack
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.4.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
|
|
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Requires-Dist: haystack-ai>=2.24.0
|
|
22
22
|
Requires-Dist: python-dateutil
|
|
23
|
-
Requires-Dist: weaviate-client>=4.
|
|
23
|
+
Requires-Dist: weaviate-client>=4.20
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
|
|
26
26
|
# weaviate-haystack
|
|
@@ -23,7 +23,7 @@ class WeaviateHybridRetriever:
|
|
|
23
23
|
document_store: WeaviateDocumentStore,
|
|
24
24
|
filters: dict[str, Any] | None = None,
|
|
25
25
|
top_k: int = 10,
|
|
26
|
-
alpha: float
|
|
26
|
+
alpha: float = 0.7,
|
|
27
27
|
max_vector_distance: float | None = None,
|
|
28
28
|
filter_policy: str | FilterPolicy = FilterPolicy.REPLACE,
|
|
29
29
|
):
|
|
@@ -46,7 +46,7 @@ class WeaviateHybridRetriever:
|
|
|
46
46
|
- `alpha = 1.0`: only vector similarity scoring is used.
|
|
47
47
|
- Values in between blend the two; higher values favor the vector score, lower values favor BM25.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
By default, 0.7 is used which is the Weaviate server default.
|
|
50
50
|
|
|
51
51
|
See the official Weaviate docs on Hybrid Search parameters for more details:
|
|
52
52
|
- [Hybrid search parameters](https://weaviate.io/developers/weaviate/search/hybrid#parameters)
|
|
@@ -66,7 +66,7 @@ class WeaviateHybridRetriever:
|
|
|
66
66
|
Policy to determine how filters are applied.
|
|
67
67
|
"""
|
|
68
68
|
|
|
69
|
-
if
|
|
69
|
+
if not 0.0 <= alpha <= 1.0:
|
|
70
70
|
msg = f"alpha ({alpha}) must be in the range [0.0, 1.0]"
|
|
71
71
|
raise ValueError(msg)
|
|
72
72
|
|
|
@@ -6,7 +6,7 @@ import base64
|
|
|
6
6
|
import datetime
|
|
7
7
|
import json
|
|
8
8
|
from dataclasses import asdict
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any, NoReturn
|
|
10
10
|
|
|
11
11
|
from haystack import logging
|
|
12
12
|
from haystack.core.serialization import default_from_dict, default_to_dict
|
|
@@ -22,9 +22,13 @@ from weaviate.collections.classes.aggregate import (
|
|
|
22
22
|
_MetricsInteger,
|
|
23
23
|
_MetricsNumber,
|
|
24
24
|
)
|
|
25
|
-
from weaviate.collections.classes.
|
|
25
|
+
from weaviate.collections.classes.batch import ErrorObject
|
|
26
|
+
from weaviate.collections.classes.internal import Object
|
|
27
|
+
from weaviate.collections.collection import Collection, CollectionAsync
|
|
28
|
+
from weaviate.collections.iterator import _ObjectAIterator, _ObjectIterator
|
|
26
29
|
from weaviate.config import AdditionalConfig
|
|
27
30
|
from weaviate.embedded import EmbeddedOptions
|
|
31
|
+
from weaviate.types import VECTORS
|
|
28
32
|
from weaviate.util import generate_uuid5
|
|
29
33
|
|
|
30
34
|
from ._filters import convert_filters, validate_filters
|
|
@@ -105,7 +109,7 @@ class WeaviateDocumentStore:
|
|
|
105
109
|
additional_config: AdditionalConfig | None = None,
|
|
106
110
|
grpc_port: int = 50051,
|
|
107
111
|
grpc_secure: bool = False,
|
|
108
|
-
):
|
|
112
|
+
) -> None:
|
|
109
113
|
"""
|
|
110
114
|
Create a new instance of WeaviateDocumentStore and connects to the Weaviate instance.
|
|
111
115
|
|
|
@@ -267,7 +271,7 @@ class WeaviateDocumentStore:
|
|
|
267
271
|
return self._async_client
|
|
268
272
|
|
|
269
273
|
@property
|
|
270
|
-
def collection(self):
|
|
274
|
+
def collection(self) -> Collection[dict[str, Any], None]:
|
|
271
275
|
if self._collection:
|
|
272
276
|
return self._collection
|
|
273
277
|
|
|
@@ -276,7 +280,7 @@ class WeaviateDocumentStore:
|
|
|
276
280
|
return self._collection
|
|
277
281
|
|
|
278
282
|
@property
|
|
279
|
-
async def async_collection(self):
|
|
283
|
+
async def async_collection(self) -> CollectionAsync[dict[str, Any], None]:
|
|
280
284
|
if self._async_collection:
|
|
281
285
|
return self._async_collection
|
|
282
286
|
|
|
@@ -352,6 +356,14 @@ class WeaviateDocumentStore:
|
|
|
352
356
|
total = self.collection.aggregate.over_all(total_count=True).total_count
|
|
353
357
|
return total if total else 0
|
|
354
358
|
|
|
359
|
+
async def count_documents_async(self) -> int:
|
|
360
|
+
"""
|
|
361
|
+
Asynchronously returns the number of documents present in the DocumentStore.
|
|
362
|
+
"""
|
|
363
|
+
collection = await self.async_collection
|
|
364
|
+
total = (await collection.aggregate.over_all(total_count=True)).total_count
|
|
365
|
+
return total if total else 0
|
|
366
|
+
|
|
355
367
|
def count_documents_by_filter(self, filters: dict[str, Any]) -> int:
|
|
356
368
|
"""
|
|
357
369
|
Returns the number of documents that match the provided filters.
|
|
@@ -733,15 +745,6 @@ class WeaviateDocumentStore:
|
|
|
733
745
|
# The embedding vector is stored separately from the rest of the data
|
|
734
746
|
del data["embedding"]
|
|
735
747
|
|
|
736
|
-
# _split_overlap meta field is unsupported because of a bug
|
|
737
|
-
# https://github.com/deepset-ai/haystack-core-integrations/issues/1172
|
|
738
|
-
if "_split_overlap" in data:
|
|
739
|
-
data.pop("_split_overlap")
|
|
740
|
-
logger.warning(
|
|
741
|
-
"Document {id} has the unsupported `_split_overlap` meta field. It will be ignored.",
|
|
742
|
-
id=data["_original_id"],
|
|
743
|
-
)
|
|
744
|
-
|
|
745
748
|
if "sparse_embedding" in data:
|
|
746
749
|
sparse_embedding = data.pop("sparse_embedding", None)
|
|
747
750
|
if sparse_embedding:
|
|
@@ -755,7 +758,7 @@ class WeaviateDocumentStore:
|
|
|
755
758
|
return data
|
|
756
759
|
|
|
757
760
|
@staticmethod
|
|
758
|
-
def _to_document(data:
|
|
761
|
+
def _to_document(data: Object[dict[str, Any], None]) -> Document:
|
|
759
762
|
"""
|
|
760
763
|
Converts a data object read from Weaviate into a Document.
|
|
761
764
|
"""
|
|
@@ -793,7 +796,7 @@ class WeaviateDocumentStore:
|
|
|
793
796
|
|
|
794
797
|
return Document.from_dict(document_data)
|
|
795
798
|
|
|
796
|
-
def _query(self) ->
|
|
799
|
+
def _query(self) -> _ObjectIterator[dict[str, Any], None]:
|
|
797
800
|
properties = [p.name for p in self.collection.config.get().properties]
|
|
798
801
|
try:
|
|
799
802
|
result = self.collection.iterator(include_vector=True, return_properties=properties)
|
|
@@ -802,7 +805,17 @@ class WeaviateDocumentStore:
|
|
|
802
805
|
raise DocumentStoreError(msg) from e
|
|
803
806
|
return result
|
|
804
807
|
|
|
805
|
-
def
|
|
808
|
+
async def _query_async(self) -> _ObjectAIterator[dict[str, Any], None]:
|
|
809
|
+
collection = await self.async_collection
|
|
810
|
+
properties = [p.name for p in (await collection.config.get()).properties]
|
|
811
|
+
try:
|
|
812
|
+
result = collection.iterator(include_vector=True, return_properties=properties)
|
|
813
|
+
except weaviate.exceptions.WeaviateQueryError as e:
|
|
814
|
+
msg = f"Failed to query documents in Weaviate. Error: {e.message}"
|
|
815
|
+
raise DocumentStoreError(msg) from e
|
|
816
|
+
return result
|
|
817
|
+
|
|
818
|
+
def _query_with_filters(self, filters: dict[str, Any]) -> list[Object[dict[str, Any], None]]:
|
|
806
819
|
properties = [p.name for p in self.collection.config.get().properties]
|
|
807
820
|
# When querying with filters we need to paginate using limit and offset as using
|
|
808
821
|
# a cursor with after is not possible. See the official docs:
|
|
@@ -833,6 +846,38 @@ class WeaviateDocumentStore:
|
|
|
833
846
|
offset += DEFAULT_QUERY_LIMIT
|
|
834
847
|
return result
|
|
835
848
|
|
|
849
|
+
async def _query_with_filters_async(self, filters: dict[str, Any]) -> list[Object[dict[str, Any], None]]:
|
|
850
|
+
collection = await self.async_collection
|
|
851
|
+
properties = [p.name for p in (await collection.config.get()).properties]
|
|
852
|
+
# When querying with filters we need to paginate using limit and offset as using
|
|
853
|
+
# a cursor with after is not possible. See the official docs:
|
|
854
|
+
# https://weaviate.io/developers/weaviate/api/graphql/additional-operators#cursor-with-after
|
|
855
|
+
#
|
|
856
|
+
# Nonetheless there's also another issue, paginating with limit and offset is not efficient
|
|
857
|
+
# and it's still restricted by the QUERY_MAXIMUM_RESULTS environment variable.
|
|
858
|
+
# If the sum of limit and offset is greater than QUERY_MAXIMUM_RESULTS an error is raised.
|
|
859
|
+
# See the official docs for more:
|
|
860
|
+
# https://weaviate.io/developers/weaviate/api/graphql/additional-operators#performance-considerations
|
|
861
|
+
offset = 0
|
|
862
|
+
partial_result = None
|
|
863
|
+
result = []
|
|
864
|
+
# Keep querying until we get all documents matching the filters
|
|
865
|
+
while partial_result is None or len(partial_result.objects) == DEFAULT_QUERY_LIMIT:
|
|
866
|
+
try:
|
|
867
|
+
partial_result = await collection.query.fetch_objects(
|
|
868
|
+
filters=convert_filters(filters),
|
|
869
|
+
include_vector=True,
|
|
870
|
+
limit=DEFAULT_QUERY_LIMIT,
|
|
871
|
+
offset=offset,
|
|
872
|
+
return_properties=properties,
|
|
873
|
+
)
|
|
874
|
+
except weaviate.exceptions.WeaviateQueryError as e:
|
|
875
|
+
msg = f"Failed to query documents in Weaviate. Error: {e.message}"
|
|
876
|
+
raise DocumentStoreError(msg) from e
|
|
877
|
+
result.extend(partial_result.objects)
|
|
878
|
+
offset += DEFAULT_QUERY_LIMIT
|
|
879
|
+
return result
|
|
880
|
+
|
|
836
881
|
def filter_documents(self, filters: dict[str, Any] | None = None) -> list[Document]:
|
|
837
882
|
"""
|
|
838
883
|
Returns the documents that match the filters provided.
|
|
@@ -852,9 +897,48 @@ class WeaviateDocumentStore:
|
|
|
852
897
|
result = []
|
|
853
898
|
if filters:
|
|
854
899
|
result = self._query_with_filters(filters)
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
return [WeaviateDocumentStore._to_document(doc) for doc in
|
|
900
|
+
return [WeaviateDocumentStore._to_document(doc) for doc in result]
|
|
901
|
+
result_iter = self._query()
|
|
902
|
+
return [WeaviateDocumentStore._to_document(doc) for doc in result_iter]
|
|
903
|
+
|
|
904
|
+
async def filter_documents_async(self, filters: dict[str, Any] | None = None) -> list[Document]:
|
|
905
|
+
"""
|
|
906
|
+
Asynchronously returns the documents that match the filters provided.
|
|
907
|
+
|
|
908
|
+
For a detailed specification of the filters, refer to the
|
|
909
|
+
DocumentStore.filter_documents() protocol documentation.
|
|
910
|
+
|
|
911
|
+
Note: The ``contains`` filter operator is case-sensitive (substring
|
|
912
|
+
matching). For case-insensitive matching, normalize the value before
|
|
913
|
+
building the filter.
|
|
914
|
+
|
|
915
|
+
:param filters: The filters to apply to the document list.
|
|
916
|
+
:returns: A list of Documents that match the given filters.
|
|
917
|
+
"""
|
|
918
|
+
validate_filters(filters)
|
|
919
|
+
|
|
920
|
+
result = []
|
|
921
|
+
if filters:
|
|
922
|
+
result = await self._query_with_filters_async(filters)
|
|
923
|
+
return [WeaviateDocumentStore._to_document(doc) for doc in result]
|
|
924
|
+
result_iter = await self._query_async()
|
|
925
|
+
return [WeaviateDocumentStore._to_document(doc) async for doc in result_iter]
|
|
926
|
+
|
|
927
|
+
@staticmethod
|
|
928
|
+
def _handle_failed_objects(failed_objects: list[ErrorObject]) -> NoReturn:
|
|
929
|
+
# We fall back to use the UUID if the _original_id is not present, this is just to be
|
|
930
|
+
mapped_objects = {}
|
|
931
|
+
for obj in failed_objects:
|
|
932
|
+
properties = obj.object_.properties or {}
|
|
933
|
+
# We get the object uuid just in case the _original_id is not present.
|
|
934
|
+
# That's extremely unlikely to happen but let's stay on the safe side.
|
|
935
|
+
id_ = properties.get("_original_id", obj.object_.uuid)
|
|
936
|
+
mapped_objects[id_] = obj.message
|
|
937
|
+
|
|
938
|
+
msg = "\n".join(
|
|
939
|
+
[f"Failed to write object with id '{id_}'. Error: '{message}'" for id_, message in mapped_objects.items()]
|
|
940
|
+
)
|
|
941
|
+
raise DocumentStoreError(msg)
|
|
858
942
|
|
|
859
943
|
def _batch_write(self, documents: list[Document]) -> int:
|
|
860
944
|
"""
|
|
@@ -876,22 +960,35 @@ class WeaviateDocumentStore:
|
|
|
876
960
|
vector=doc.embedding,
|
|
877
961
|
)
|
|
878
962
|
if failed_objects := self.client.batch.failed_objects:
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
963
|
+
self._handle_failed_objects(failed_objects)
|
|
964
|
+
|
|
965
|
+
# If the document already exists we get no status message back from Weaviate.
|
|
966
|
+
# So we assume that all Documents were written.
|
|
967
|
+
return len(documents)
|
|
968
|
+
|
|
969
|
+
async def _batch_write_async(self, documents: list[Document]) -> int:
|
|
970
|
+
"""
|
|
971
|
+
Asynchronously writes document to Weaviate in batches.
|
|
972
|
+
Documents with the same id will be overwritten.
|
|
973
|
+
Raises in case of errors.
|
|
974
|
+
"""
|
|
975
|
+
client = await self.async_client
|
|
976
|
+
|
|
977
|
+
async with client.batch.stream() as batch:
|
|
978
|
+
for doc in documents:
|
|
979
|
+
if not isinstance(doc, Document):
|
|
980
|
+
msg = f"Expected a Document, got '{type(doc)}' instead."
|
|
981
|
+
raise ValueError(msg)
|
|
982
|
+
|
|
983
|
+
await batch.add_object(
|
|
984
|
+
properties=WeaviateDocumentStore._to_data_object(doc),
|
|
985
|
+
collection=(await self.async_collection).name,
|
|
986
|
+
uuid=generate_uuid5(doc.id),
|
|
987
|
+
vector=doc.embedding,
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
if failed_objects := client.batch.failed_objects:
|
|
991
|
+
self._handle_failed_objects(failed_objects)
|
|
895
992
|
|
|
896
993
|
# If the document already exists we get no status message back from Weaviate.
|
|
897
994
|
# So we assume that all Documents were written.
|
|
@@ -931,6 +1028,43 @@ class WeaviateDocumentStore:
|
|
|
931
1028
|
raise DuplicateDocumentError(msg)
|
|
932
1029
|
return written
|
|
933
1030
|
|
|
1031
|
+
async def _write_async(self, documents: list[Document], policy: DuplicatePolicy) -> int:
|
|
1032
|
+
"""
|
|
1033
|
+
Asynchronously writes documents to Weaviate using the specified policy.
|
|
1034
|
+
This doesn't use the batch API, so it's slower than _batch_write.
|
|
1035
|
+
If policy is set to SKIP it will skip any document that already exists.
|
|
1036
|
+
If policy is set to FAIL it will raise an exception if any of the documents already exists.
|
|
1037
|
+
"""
|
|
1038
|
+
collection = await self.async_collection
|
|
1039
|
+
|
|
1040
|
+
duplicate_errors_ids = []
|
|
1041
|
+
for doc in documents:
|
|
1042
|
+
if not isinstance(doc, Document):
|
|
1043
|
+
msg = f"Expected a Document, got '{type(doc)}' instead."
|
|
1044
|
+
raise ValueError(msg)
|
|
1045
|
+
|
|
1046
|
+
if policy == DuplicatePolicy.SKIP and await (await self.async_collection).data.exists(
|
|
1047
|
+
uuid=generate_uuid5(doc.id)
|
|
1048
|
+
):
|
|
1049
|
+
# This Document already exists, continue
|
|
1050
|
+
continue
|
|
1051
|
+
|
|
1052
|
+
try:
|
|
1053
|
+
await collection.data.insert(
|
|
1054
|
+
uuid=generate_uuid5(doc.id),
|
|
1055
|
+
properties=WeaviateDocumentStore._to_data_object(doc),
|
|
1056
|
+
vector=doc.embedding,
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
except weaviate.exceptions.UnexpectedStatusCodeError:
|
|
1060
|
+
if policy == DuplicatePolicy.FAIL:
|
|
1061
|
+
duplicate_errors_ids.append(doc.id)
|
|
1062
|
+
|
|
1063
|
+
if duplicate_errors_ids:
|
|
1064
|
+
msg = f"IDs '{', '.join(duplicate_errors_ids)}' already exist in the document store."
|
|
1065
|
+
raise DuplicateDocumentError(msg)
|
|
1066
|
+
return len(documents)
|
|
1067
|
+
|
|
934
1068
|
def write_documents(self, documents: list[Document], policy: DuplicatePolicy = DuplicatePolicy.NONE) -> int:
|
|
935
1069
|
"""
|
|
936
1070
|
Writes documents to Weaviate using the specified policy.
|
|
@@ -939,12 +1073,59 @@ class WeaviateDocumentStore:
|
|
|
939
1073
|
We can't use the batch API for other policies as it doesn't return any information whether the document
|
|
940
1074
|
already exists or not. That prevents us from returning errors when using the FAIL policy or skipping a
|
|
941
1075
|
Document when using the SKIP policy.
|
|
1076
|
+
|
|
1077
|
+
:param documents:
|
|
1078
|
+
A list of documents to write into the document store.
|
|
1079
|
+
:param policy:
|
|
1080
|
+
DuplicatePolicy to apply when a document with the same ID already exists in the document store.
|
|
1081
|
+
|
|
1082
|
+
:raises ValueError:
|
|
1083
|
+
When input is not valid.
|
|
1084
|
+
:raises DuplicateDocumentError:
|
|
1085
|
+
When duplicate documents are found and using a FAIL policy.
|
|
1086
|
+
:raises DocumentStoreError:
|
|
1087
|
+
When documents have failed to be batch written.
|
|
1088
|
+
|
|
1089
|
+
:returns:
|
|
1090
|
+
The number of documents written.
|
|
942
1091
|
"""
|
|
943
1092
|
if policy in [DuplicatePolicy.NONE, DuplicatePolicy.OVERWRITE]:
|
|
944
1093
|
return self._batch_write(documents)
|
|
945
1094
|
|
|
946
1095
|
return self._write(documents, policy)
|
|
947
1096
|
|
|
1097
|
+
async def write_documents_async(
|
|
1098
|
+
self, documents: list[Document], policy: DuplicatePolicy = DuplicatePolicy.NONE
|
|
1099
|
+
) -> int:
|
|
1100
|
+
"""
|
|
1101
|
+
Asynchronously writes documents to Weaviate using the specified policy.
|
|
1102
|
+
We recommend using a OVERWRITE policy as it's faster than other policies for Weaviate since it uses
|
|
1103
|
+
the batch API.
|
|
1104
|
+
We can't use the batch API for other policies as it doesn't return any information whether the document
|
|
1105
|
+
already exists or not. That prevents us from returning errors when using the FAIL policy or skipping a
|
|
1106
|
+
Document when using the SKIP policy.
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
:param documents:
|
|
1110
|
+
A list of documents to write into the document store.
|
|
1111
|
+
:param policy:
|
|
1112
|
+
DuplicatePolicy to apply when a document with the same ID already exists in the document store.
|
|
1113
|
+
|
|
1114
|
+
:raises ValueError:
|
|
1115
|
+
When input is not valid.
|
|
1116
|
+
:raises DuplicateDocumentError:
|
|
1117
|
+
When duplicate documents are found and using a FAIL policy.
|
|
1118
|
+
:raises DocumentStoreError:
|
|
1119
|
+
When documents have failed to be batch written.
|
|
1120
|
+
|
|
1121
|
+
:returns:
|
|
1122
|
+
The number of documents written.
|
|
1123
|
+
"""
|
|
1124
|
+
if policy in [DuplicatePolicy.NONE, DuplicatePolicy.OVERWRITE]:
|
|
1125
|
+
return await self._batch_write_async(documents)
|
|
1126
|
+
|
|
1127
|
+
return await self._write_async(documents, policy)
|
|
1128
|
+
|
|
948
1129
|
def delete_documents(self, document_ids: list[str]) -> None:
|
|
949
1130
|
"""
|
|
950
1131
|
Deletes all documents with matching document_ids from the DocumentStore.
|
|
@@ -954,6 +1135,16 @@ class WeaviateDocumentStore:
|
|
|
954
1135
|
weaviate_ids = [generate_uuid5(doc_id) for doc_id in document_ids]
|
|
955
1136
|
self.collection.data.delete_many(where=weaviate.classes.query.Filter.by_id().contains_any(weaviate_ids))
|
|
956
1137
|
|
|
1138
|
+
async def delete_documents_async(self, document_ids: list[str]) -> None:
|
|
1139
|
+
"""
|
|
1140
|
+
Asynchronously deletes all documents with matching document_ids from the DocumentStore.
|
|
1141
|
+
|
|
1142
|
+
:param document_ids: The object_ids to delete.
|
|
1143
|
+
"""
|
|
1144
|
+
weaviate_ids = [generate_uuid5(doc_id) for doc_id in document_ids]
|
|
1145
|
+
collection = await self.async_collection
|
|
1146
|
+
await collection.data.delete_many(where=weaviate.classes.query.Filter.by_id().contains_any(weaviate_ids))
|
|
1147
|
+
|
|
957
1148
|
def delete_all_documents(self, *, recreate_index: bool = False, batch_size: int = 1000) -> None:
|
|
958
1149
|
"""
|
|
959
1150
|
Deletes all documents in a collection.
|
|
@@ -1003,6 +1194,57 @@ class WeaviateDocumentStore:
|
|
|
1003
1194
|
"Make sure to specify a deletion `batch_size` which is less than `QUERY_MAXIMUM_RESULTS`.",
|
|
1004
1195
|
)
|
|
1005
1196
|
|
|
1197
|
+
async def delete_all_documents_async(self, *, recreate_index: bool = False, batch_size: int = 1000) -> None:
|
|
1198
|
+
"""
|
|
1199
|
+
Asynchronously deletes all documents in a collection.
|
|
1200
|
+
|
|
1201
|
+
If recreate_index is False, it keeps the collection but deletes documents iteratively.
|
|
1202
|
+
If recreate_index is True, the collection is dropped and faithfully recreated.
|
|
1203
|
+
This is recommended for performance reasons.
|
|
1204
|
+
|
|
1205
|
+
:param recreate_index: Use drop and recreate strategy. (recommended for performance)
|
|
1206
|
+
:param batch_size: Only relevant if recreate_index is false. Defines the deletion batch size.
|
|
1207
|
+
Note that this parameter needs to be less or equal to the set `QUERY_MAXIMUM_RESULTS` variable
|
|
1208
|
+
set for the weaviate deployment (default is 10000).
|
|
1209
|
+
Reference: https://docs.weaviate.io/weaviate/manage-objects/delete#delete-all-objects
|
|
1210
|
+
"""
|
|
1211
|
+
client = await self.async_client
|
|
1212
|
+
|
|
1213
|
+
if recreate_index:
|
|
1214
|
+
# get current up-to-date config from server, so we can recreate the collection faithfully
|
|
1215
|
+
cfg = (await client.collections.get(self._collection_settings["class"]).config.get()).to_dict()
|
|
1216
|
+
class_name = cfg.get("class", self._collection_settings["class"])
|
|
1217
|
+
|
|
1218
|
+
await client.collections.delete(class_name)
|
|
1219
|
+
await client.collections.create_from_dict(cfg)
|
|
1220
|
+
|
|
1221
|
+
self._collection_settings = cfg
|
|
1222
|
+
self._async_collection = client.collections.get(class_name)
|
|
1223
|
+
return
|
|
1224
|
+
|
|
1225
|
+
uuids = []
|
|
1226
|
+
batch_size = max(1, int(batch_size))
|
|
1227
|
+
|
|
1228
|
+
collection = await self.async_collection
|
|
1229
|
+
async for obj in collection.iterator(return_properties=[], include_vector=False):
|
|
1230
|
+
uuids.append(obj.uuid)
|
|
1231
|
+
if len(uuids) >= batch_size:
|
|
1232
|
+
res = await collection.data.delete_many(where=weaviate.classes.query.Filter.by_id().contains_any(uuids))
|
|
1233
|
+
if res.successful < len(uuids):
|
|
1234
|
+
logger.warning(
|
|
1235
|
+
"Not all documents in the batch have been deleted. "
|
|
1236
|
+
"Make sure to specify a deletion `batch_size` which is less than `QUERY_MAXIMUM_RESULTS`.",
|
|
1237
|
+
)
|
|
1238
|
+
uuids.clear()
|
|
1239
|
+
|
|
1240
|
+
if uuids:
|
|
1241
|
+
res = await collection.data.delete_many(where=weaviate.classes.query.Filter.by_id().contains_any(uuids))
|
|
1242
|
+
if res.successful < len(uuids):
|
|
1243
|
+
logger.warning(
|
|
1244
|
+
"Not all documents have been deleted. "
|
|
1245
|
+
"Make sure to specify a deletion `batch_size` which is less than `QUERY_MAXIMUM_RESULTS`.",
|
|
1246
|
+
)
|
|
1247
|
+
|
|
1006
1248
|
def delete_by_filter(self, filters: dict[str, Any]) -> int:
|
|
1007
1249
|
"""
|
|
1008
1250
|
Deletes all documents that match the provided filters.
|
|
@@ -1095,11 +1337,9 @@ class WeaviateDocumentStore:
|
|
|
1095
1337
|
|
|
1096
1338
|
# Update the object, preserving the vector
|
|
1097
1339
|
# Get the vector from the object to preserve it during replace
|
|
1098
|
-
vector = None
|
|
1099
|
-
if isinstance(obj.vector, list):
|
|
1340
|
+
vector: VECTORS | None = None
|
|
1341
|
+
if isinstance(obj.vector, (list, dict)):
|
|
1100
1342
|
vector = obj.vector
|
|
1101
|
-
elif isinstance(obj.vector, dict):
|
|
1102
|
-
vector = obj.vector.get("default")
|
|
1103
1343
|
|
|
1104
1344
|
self.collection.data.replace(
|
|
1105
1345
|
uuid=obj.uuid,
|
|
@@ -1191,11 +1431,9 @@ class WeaviateDocumentStore:
|
|
|
1191
1431
|
|
|
1192
1432
|
# Update the object, preserving the vector
|
|
1193
1433
|
# Get the vector from the object to preserve it during replace
|
|
1194
|
-
vector = None
|
|
1195
|
-
if isinstance(obj.vector, list):
|
|
1434
|
+
vector: VECTORS | None = None
|
|
1435
|
+
if isinstance(obj.vector, (list, dict)):
|
|
1196
1436
|
vector = obj.vector
|
|
1197
|
-
elif isinstance(obj.vector, dict):
|
|
1198
|
-
vector = obj.vector.get("default")
|
|
1199
1437
|
|
|
1200
1438
|
await collection.data.replace(
|
|
1201
1439
|
uuid=obj.uuid,
|
|
@@ -1322,7 +1560,7 @@ class WeaviateDocumentStore:
|
|
|
1322
1560
|
query_embedding: list[float],
|
|
1323
1561
|
filters: dict[str, Any] | None = None,
|
|
1324
1562
|
top_k: int | None = None,
|
|
1325
|
-
alpha: float
|
|
1563
|
+
alpha: float = 0.7,
|
|
1326
1564
|
max_vector_distance: float | None = None,
|
|
1327
1565
|
) -> list[Document]:
|
|
1328
1566
|
properties = [p.name for p in self.collection.config.get().properties]
|
|
@@ -1347,7 +1585,7 @@ class WeaviateDocumentStore:
|
|
|
1347
1585
|
query_embedding: list[float],
|
|
1348
1586
|
filters: dict[str, Any] | None = None,
|
|
1349
1587
|
top_k: int | None = None,
|
|
1350
|
-
alpha: float
|
|
1588
|
+
alpha: float = 0.7,
|
|
1351
1589
|
max_vector_distance: float | None = None,
|
|
1352
1590
|
) -> list[Document]:
|
|
1353
1591
|
collection = await self.async_collection
|
|
@@ -464,29 +464,32 @@ class TestWeaviateDocumentStore(DocumentStoreBaseExtendedTests):
|
|
|
464
464
|
],
|
|
465
465
|
)
|
|
466
466
|
|
|
467
|
-
def
|
|
467
|
+
def test_split_overlap_preserved(self, document_store):
|
|
468
|
+
"""Split overlap meta is written and read back correctly."""
|
|
469
|
+
overlap = [
|
|
470
|
+
{"range": [3.0, 13.0], "doc_id": "34326b7e6be489cb4c031152fc378cb50479ca5fcc3861e7e61dfb2e4e4e968b"},
|
|
471
|
+
{"range": [0.0, 13.0], "doc_id": "780f791c09d499c0bf01f87bce047b45c44224d36c79f0c9d8c1405a3197fc1a"},
|
|
472
|
+
]
|
|
468
473
|
doc = Document(
|
|
469
|
-
|
|
474
|
+
id="6edd24e8b01f3cd6e4b71fef7d57b52f17664e14db5ab01b8ef429f97add3620",
|
|
475
|
+
content="an eighth test. ",
|
|
470
476
|
meta={
|
|
471
|
-
"
|
|
472
|
-
"page_number": 1,
|
|
473
|
-
"split_id": 0,
|
|
474
|
-
"split_idx_start": 0,
|
|
475
|
-
"
|
|
476
|
-
{"doc_id": "68ed48ba830048c5d7815874ed2de794722e6d10866b6c55349a914fd9a0df65", "range": (0, 20)}
|
|
477
|
-
],
|
|
477
|
+
"_split_overlap": overlap,
|
|
478
|
+
"page_number": 1.0,
|
|
479
|
+
"split_id": 33.0,
|
|
480
|
+
"split_idx_start": 159.0,
|
|
481
|
+
"source_id": "fdbde6d217f04d3dd60c01f36541794f3153a61f13b4ca669655f4c5610c1664",
|
|
478
482
|
},
|
|
479
483
|
)
|
|
480
484
|
document_store.write_documents([doc])
|
|
481
|
-
|
|
482
485
|
written_doc = document_store.filter_documents()[0]
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
assert
|
|
486
|
-
assert
|
|
487
|
-
assert
|
|
488
|
-
assert
|
|
489
|
-
assert "
|
|
486
|
+
assert "_split_overlap" in written_doc.meta
|
|
487
|
+
written_overlap = written_doc.meta["_split_overlap"]
|
|
488
|
+
assert len(written_overlap) == 2
|
|
489
|
+
assert written_overlap[0]["doc_id"] == overlap[0]["doc_id"]
|
|
490
|
+
assert list(written_overlap[0]["range"]) == [3, 13]
|
|
491
|
+
assert written_overlap[1]["doc_id"] == overlap[1]["doc_id"]
|
|
492
|
+
assert list(written_overlap[1]["range"]) == [0, 13]
|
|
490
493
|
|
|
491
494
|
def test_bm25_retrieval(self, document_store):
|
|
492
495
|
document_store.write_documents(
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
from collections.abc import AsyncGenerator
|
|
7
|
+
from pathlib import Path
|
|
6
8
|
|
|
7
9
|
import pytest
|
|
8
10
|
import pytest_asyncio
|
|
11
|
+
from haystack.dataclasses.byte_stream import ByteStream
|
|
9
12
|
from haystack.dataclasses.document import Document
|
|
13
|
+
from haystack.document_stores.errors import DocumentStoreError
|
|
10
14
|
|
|
11
15
|
from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore
|
|
12
16
|
from haystack_integrations.document_stores.weaviate.document_store import DOCUMENT_COLLECTION_PROPERTIES
|
|
@@ -31,8 +35,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
31
35
|
collection_settings=collection_settings,
|
|
32
36
|
)
|
|
33
37
|
yield store
|
|
34
|
-
store.
|
|
35
|
-
store.close()
|
|
38
|
+
await (await store.async_client).collections.delete(collection_settings["class"])
|
|
36
39
|
await store.close_async()
|
|
37
40
|
|
|
38
41
|
@pytest.mark.asyncio
|
|
@@ -50,20 +53,68 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
50
53
|
assert await document_store.async_client is not None
|
|
51
54
|
assert await document_store.async_collection is not None
|
|
52
55
|
|
|
53
|
-
document_store.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
assert await document_store.count_documents_async() == 0
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_filter_documents_with_blob_data_async(
|
|
60
|
+
self, document_store: WeaviateDocumentStore, test_files_path: Path
|
|
61
|
+
) -> None:
|
|
62
|
+
image = ByteStream.from_file_path(test_files_path / "robot1.jpg", mime_type="image/jpeg")
|
|
63
|
+
doc = Document(content="test doc", blob=image)
|
|
64
|
+
assert await document_store.write_documents_async([doc]) == 1
|
|
65
|
+
|
|
66
|
+
docs = await document_store.filter_documents_async()
|
|
67
|
+
|
|
68
|
+
assert len(docs) == 1
|
|
69
|
+
assert docs[0].blob == image
|
|
70
|
+
|
|
71
|
+
@pytest.mark.asyncio
|
|
72
|
+
async def test_filter_documents_below_default_limit_async(self, document_store: WeaviateDocumentStore) -> None:
|
|
73
|
+
docs = []
|
|
74
|
+
for index in range(9998):
|
|
75
|
+
docs.append(Document(content="This is some content", meta={"index": index}))
|
|
76
|
+
await document_store.write_documents_async(docs)
|
|
77
|
+
result = await document_store.filter_documents_async(
|
|
78
|
+
{"field": "content", "operator": "==", "value": "This is some content"}
|
|
59
79
|
)
|
|
60
|
-
filters = {"field": "content", "operator": "==", "value": "Haskell"}
|
|
61
80
|
|
|
62
|
-
assert
|
|
81
|
+
assert len(result) == 9998
|
|
82
|
+
|
|
83
|
+
@pytest.mark.asyncio
|
|
84
|
+
async def test_filter_documents_over_default_limit(self, document_store: WeaviateDocumentStore) -> None:
|
|
85
|
+
docs = []
|
|
86
|
+
for index in range(10000):
|
|
87
|
+
docs.append(Document(content="This is some content", meta={"index": index}))
|
|
88
|
+
await document_store.write_documents_async(docs)
|
|
89
|
+
with pytest.raises(DocumentStoreError):
|
|
90
|
+
await document_store.filter_documents_async(
|
|
91
|
+
{"field": "content", "operator": "==", "value": "This is some content"}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
@pytest.mark.asyncio
|
|
95
|
+
async def test_write_documents_async(self, document_store: WeaviateDocumentStore) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Test write_documents() with default policy overwrites existing documents.
|
|
98
|
+
"""
|
|
99
|
+
doc = Document(content="test doc")
|
|
100
|
+
assert await document_store.write_documents_async([doc]) == 1
|
|
101
|
+
assert await document_store.count_documents_async() == 1
|
|
102
|
+
|
|
103
|
+
doc.content = "test doc 2"
|
|
104
|
+
assert await document_store.write_documents_async([doc]) == 1
|
|
105
|
+
assert await document_store.count_documents_async() == 1
|
|
106
|
+
|
|
107
|
+
@pytest.mark.asyncio
|
|
108
|
+
async def test_write_documents_with_blob_data_async(
|
|
109
|
+
self, document_store: WeaviateDocumentStore, test_files_path: Path
|
|
110
|
+
) -> None:
|
|
111
|
+
image = ByteStream.from_file_path(test_files_path / "robot1.jpg", mime_type="image/jpeg")
|
|
112
|
+
doc = Document(content="test doc", blob=image)
|
|
113
|
+
assert await document_store.write_documents_async([doc]) == 1
|
|
63
114
|
|
|
64
115
|
@pytest.mark.asyncio
|
|
65
116
|
async def test_bm25_retrieval_async(self, document_store):
|
|
66
|
-
document_store.
|
|
117
|
+
await document_store.write_documents_async(
|
|
67
118
|
[
|
|
68
119
|
Document(content="Haskell is a functional programming language"),
|
|
69
120
|
Document(content="Lisp is a functional programming language"),
|
|
@@ -80,7 +131,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
80
131
|
|
|
81
132
|
@pytest.mark.asyncio
|
|
82
133
|
async def test_bm25_retrieval_async_with_filters(self, document_store):
|
|
83
|
-
document_store.
|
|
134
|
+
await document_store.write_documents_async(
|
|
84
135
|
[
|
|
85
136
|
Document(content="Haskell is a functional programming language"),
|
|
86
137
|
Document(content="Lisp is a functional programming language"),
|
|
@@ -96,7 +147,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
96
147
|
|
|
97
148
|
@pytest.mark.asyncio
|
|
98
149
|
async def test_embedding_retrieval_async(self, document_store):
|
|
99
|
-
document_store.
|
|
150
|
+
await document_store.write_documents_async(
|
|
100
151
|
[
|
|
101
152
|
Document(content="The document", embedding=[1.0, 1.0, 1.0, 1.0]),
|
|
102
153
|
Document(content="Another document", embedding=[0.8, 0.8, 0.8, 1.0]),
|
|
@@ -112,7 +163,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
112
163
|
|
|
113
164
|
@pytest.mark.asyncio
|
|
114
165
|
async def test_embedding_retrieval_async_with_filters(self, document_store):
|
|
115
|
-
document_store.
|
|
166
|
+
await document_store.write_documents_async(
|
|
116
167
|
[
|
|
117
168
|
Document(content="The document I want", embedding=[1.0, 1.0, 1.0, 1.0]),
|
|
118
169
|
Document(content="Another document", embedding=[0.8, 0.8, 0.8, 1.0]),
|
|
@@ -134,7 +185,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
134
185
|
|
|
135
186
|
@pytest.mark.asyncio
|
|
136
187
|
async def test_hybrid_retrieval_async(self, document_store):
|
|
137
|
-
document_store.
|
|
188
|
+
await document_store.write_documents_async(
|
|
138
189
|
[
|
|
139
190
|
Document(content="Haskell is a functional programming language", embedding=[1.0, 0.8, 0.2, 0.1]),
|
|
140
191
|
Document(content="Lisp is a functional programming language", embedding=[0.9, 0.7, 0.3, 0.2]),
|
|
@@ -154,7 +205,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
154
205
|
|
|
155
206
|
@pytest.mark.asyncio
|
|
156
207
|
async def test_hybrid_retrieval_async_with_filters(self, document_store):
|
|
157
|
-
document_store.
|
|
208
|
+
await document_store.write_documents_async(
|
|
158
209
|
[
|
|
159
210
|
Document(content="Haskell is a functional programming language", embedding=[1.0, 0.8, 0.2, 0.1]),
|
|
160
211
|
Document(content="Lisp is a functional programming language", embedding=[0.9, 0.7, 0.3, 0.2]),
|
|
@@ -173,7 +224,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
173
224
|
|
|
174
225
|
@pytest.mark.asyncio
|
|
175
226
|
async def test_hybrid_retrieval_async_with_alpha(self, document_store):
|
|
176
|
-
document_store.
|
|
227
|
+
await document_store.write_documents_async(
|
|
177
228
|
[
|
|
178
229
|
Document(content="Haskell is a functional programming language", embedding=[1.0, 0.8, 0.2, 0.1]),
|
|
179
230
|
Document(content="Python is an object oriented language", embedding=[0.1, 0.2, 0.8, 0.9]),
|
|
@@ -205,7 +256,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
205
256
|
Document(content="Doc 2", meta={"category": "TypeB"}),
|
|
206
257
|
Document(content="Doc 3", meta={"category": "TypeA"}),
|
|
207
258
|
]
|
|
208
|
-
document_store.
|
|
259
|
+
await document_store.write_documents_async(docs)
|
|
209
260
|
|
|
210
261
|
# delete documents with category="TypeA"
|
|
211
262
|
deleted_count = await document_store.delete_by_filter_async(
|
|
@@ -215,7 +266,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
215
266
|
assert document_store.count_documents() == 1
|
|
216
267
|
|
|
217
268
|
# verify only category TypeB remains
|
|
218
|
-
remaining_docs = document_store.
|
|
269
|
+
remaining_docs = await document_store.filter_documents_async()
|
|
219
270
|
assert len(remaining_docs) == 1
|
|
220
271
|
assert remaining_docs[0].meta["category"] == "TypeB"
|
|
221
272
|
|
|
@@ -226,7 +277,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
226
277
|
Document(content="Doc 2", meta={"category": "TypeB", "status": "draft"}),
|
|
227
278
|
Document(content="Doc 3", meta={"category": "TypeA", "status": "draft"}),
|
|
228
279
|
]
|
|
229
|
-
document_store.
|
|
280
|
+
await document_store.write_documents_async(docs)
|
|
230
281
|
assert document_store.count_documents() == 3
|
|
231
282
|
|
|
232
283
|
# update status for category="TypeA" documents
|
|
@@ -236,7 +287,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
236
287
|
assert updated_count == 2
|
|
237
288
|
|
|
238
289
|
# Verify the updates
|
|
239
|
-
published_docs = document_store.
|
|
290
|
+
published_docs = await document_store.filter_documents_async(
|
|
240
291
|
filters={"field": "meta.status", "operator": "==", "value": "published"}
|
|
241
292
|
)
|
|
242
293
|
assert len(published_docs) == 2
|
|
@@ -257,7 +308,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
257
308
|
meta={"index": index, "status": "draft", "category": "test"},
|
|
258
309
|
)
|
|
259
310
|
)
|
|
260
|
-
document_store.
|
|
311
|
+
await document_store.write_documents_async(docs)
|
|
261
312
|
|
|
262
313
|
# update all documents should trigger pagination (3 pages)
|
|
263
314
|
updated_count = await document_store.update_by_filter_async(
|
|
@@ -266,7 +317,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
266
317
|
)
|
|
267
318
|
assert updated_count == 250
|
|
268
319
|
|
|
269
|
-
published_docs = document_store.
|
|
320
|
+
published_docs = await document_store.filter_documents_async(
|
|
270
321
|
filters={"field": "status", "operator": "==", "value": "published"}
|
|
271
322
|
)
|
|
272
323
|
assert len(published_docs) == 250
|
|
@@ -276,6 +327,17 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
276
327
|
assert "index" in doc.meta
|
|
277
328
|
assert 0 <= doc.meta["index"] < 250
|
|
278
329
|
|
|
330
|
+
@pytest.mark.asyncio
|
|
331
|
+
async def test_count_documents_async(self, document_store: WeaviateDocumentStore) -> None:
|
|
332
|
+
docs = [
|
|
333
|
+
Document(content="Doc 1", meta={"category": "TypeA"}),
|
|
334
|
+
Document(content="Doc 2", meta={"category": "TypeB"}),
|
|
335
|
+
Document(content="Doc 3", meta={"category": "TypeA"}),
|
|
336
|
+
Document(content="Doc 4", meta={"category": "TypeA"}),
|
|
337
|
+
]
|
|
338
|
+
await document_store.write_documents_async(docs)
|
|
339
|
+
assert await document_store.count_documents_async() == 4
|
|
340
|
+
|
|
279
341
|
@pytest.mark.asyncio
|
|
280
342
|
async def test_count_documents_by_filter_async(self, document_store):
|
|
281
343
|
docs = [
|
|
@@ -284,8 +346,8 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
284
346
|
Document(content="Doc 3", meta={"category": "TypeA"}),
|
|
285
347
|
Document(content="Doc 4", meta={"category": "TypeA"}),
|
|
286
348
|
]
|
|
287
|
-
document_store.
|
|
288
|
-
assert document_store.
|
|
349
|
+
await document_store.write_documents_async(docs)
|
|
350
|
+
assert await document_store.count_documents_async() == 4
|
|
289
351
|
|
|
290
352
|
count = await document_store.count_documents_by_filter_async(
|
|
291
353
|
filters={"field": "meta.category", "operator": "==", "value": "TypeA"}
|
|
@@ -325,7 +387,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
325
387
|
Document(content="Doc 3", meta={"number": 20}),
|
|
326
388
|
Document(content="Doc 4", meta={"number": 15}),
|
|
327
389
|
]
|
|
328
|
-
document_store.
|
|
390
|
+
await document_store.write_documents_async(docs)
|
|
329
391
|
|
|
330
392
|
result = await document_store.get_metadata_field_min_max_async("number")
|
|
331
393
|
assert result["min"] == 5
|
|
@@ -337,7 +399,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
337
399
|
Document(content="Doc 1", meta={"number": 100}),
|
|
338
400
|
Document(content="Doc 2", meta={"number": 200}),
|
|
339
401
|
]
|
|
340
|
-
document_store.
|
|
402
|
+
await document_store.write_documents_async(docs)
|
|
341
403
|
|
|
342
404
|
result = await document_store.get_metadata_field_min_max_async("meta.number")
|
|
343
405
|
assert result["min"] == 100
|
|
@@ -362,7 +424,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
362
424
|
Document(content="Doc 4", meta={"category": "TypeC", "status": "published"}),
|
|
363
425
|
Document(content="Doc 5", meta={"category": "TypeA", "status": "archived"}),
|
|
364
426
|
]
|
|
365
|
-
document_store.
|
|
427
|
+
await document_store.write_documents_async(docs)
|
|
366
428
|
|
|
367
429
|
result = await document_store.count_unique_metadata_by_filter_async(
|
|
368
430
|
filters={"field": "meta.category", "operator": "==", "value": "TypeA"}, metadata_fields=["status"]
|
|
@@ -388,7 +450,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
388
450
|
Document(content="Doc 1", meta={"category": "TypeA"}),
|
|
389
451
|
Document(content="Doc 2", meta={"category": "TypeB"}),
|
|
390
452
|
]
|
|
391
|
-
document_store.
|
|
453
|
+
await document_store.write_documents_async(docs)
|
|
392
454
|
|
|
393
455
|
result = await document_store.count_unique_metadata_by_filter_async(
|
|
394
456
|
filters={"field": "meta.category", "operator": "in", "value": ["TypeA", "TypeB"]},
|
|
@@ -401,7 +463,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
401
463
|
docs = [
|
|
402
464
|
Document(content="Doc 1", meta={"category": "TypeA"}),
|
|
403
465
|
]
|
|
404
|
-
document_store.
|
|
466
|
+
await document_store.write_documents_async(docs)
|
|
405
467
|
|
|
406
468
|
result = await document_store.count_unique_metadata_by_filter_async(
|
|
407
469
|
filters={"field": "meta.category", "operator": "==", "value": "NonExistent"},
|
|
@@ -426,7 +488,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
426
488
|
Document(content="Doc 4", meta={"category": "TypeC"}),
|
|
427
489
|
Document(content="Doc 5", meta={"category": "TypeB"}),
|
|
428
490
|
]
|
|
429
|
-
document_store.
|
|
491
|
+
await document_store.write_documents_async(docs)
|
|
430
492
|
|
|
431
493
|
values, total_count = await document_store.get_metadata_field_unique_values_async("category")
|
|
432
494
|
assert total_count == 3
|
|
@@ -438,7 +500,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
438
500
|
Document(content="Doc 1", meta={"category": "TypeA"}),
|
|
439
501
|
Document(content="Doc 2", meta={"category": "TypeB"}),
|
|
440
502
|
]
|
|
441
|
-
document_store.
|
|
503
|
+
await document_store.write_documents_async(docs)
|
|
442
504
|
|
|
443
505
|
values, total_count = await document_store.get_metadata_field_unique_values_async("meta.category")
|
|
444
506
|
assert total_count == 2
|
|
@@ -452,7 +514,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
452
514
|
Document(content="Python is great", meta={"category": "TypeC"}),
|
|
453
515
|
Document(content="JavaScript tutorial", meta={"category": "TypeD"}),
|
|
454
516
|
]
|
|
455
|
-
document_store.
|
|
517
|
+
await document_store.write_documents_async(docs)
|
|
456
518
|
|
|
457
519
|
values, total_count = await document_store.get_metadata_field_unique_values_async(
|
|
458
520
|
"category", search_term="Python"
|
|
@@ -469,7 +531,7 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
469
531
|
Document(content="Doc 4", meta={"category": "TypeD"}),
|
|
470
532
|
Document(content="Doc 5", meta={"category": "TypeE"}),
|
|
471
533
|
]
|
|
472
|
-
document_store.
|
|
534
|
+
await document_store.write_documents_async(docs)
|
|
473
535
|
|
|
474
536
|
values, total_count = await document_store.get_metadata_field_unique_values_async("category", from_=0, size=2)
|
|
475
537
|
assert total_count == 5
|
|
@@ -491,3 +553,39 @@ class TestWeaviateDocumentStoreAsync:
|
|
|
491
553
|
values, total_count = await document_store.get_metadata_field_unique_values_async("category")
|
|
492
554
|
assert total_count == 0
|
|
493
555
|
assert values == []
|
|
556
|
+
|
|
557
|
+
@pytest.mark.asyncio
|
|
558
|
+
async def test_delete_all_documents_recreate_async(self, document_store: WeaviateDocumentStore) -> None:
|
|
559
|
+
docs = [Document(content="test doc 1"), Document(content="test doc 2")]
|
|
560
|
+
assert await document_store.write_documents_async(docs) == 2
|
|
561
|
+
assert await document_store.count_documents_async() == 2
|
|
562
|
+
|
|
563
|
+
cls = document_store._collection_settings["class"]
|
|
564
|
+
collection = (await document_store.async_client).collections.get(cls)
|
|
565
|
+
previous_config = (await collection.config.get()).to_dict()
|
|
566
|
+
|
|
567
|
+
await document_store.delete_all_documents_async(recreate_index=True)
|
|
568
|
+
assert await document_store.count_documents_async() == 0
|
|
569
|
+
|
|
570
|
+
new_config = (await (await document_store.async_client).collections.get(cls).config.get()).to_dict()
|
|
571
|
+
assert previous_config == new_config
|
|
572
|
+
|
|
573
|
+
@pytest.mark.asyncio
|
|
574
|
+
async def test_delete_all_documents_batch_size_async(self, document_store: WeaviateDocumentStore) -> None:
|
|
575
|
+
docs = [Document(content=str(i)) for i in range(0, 5)]
|
|
576
|
+
assert await document_store.write_documents_async(docs) == 5
|
|
577
|
+
await document_store.delete_all_documents_async(batch_size=2)
|
|
578
|
+
assert await document_store.count_documents_async() == 0
|
|
579
|
+
|
|
580
|
+
@pytest.mark.asyncio
|
|
581
|
+
async def test_delete_all_documents_excessive_batch_size_async(
|
|
582
|
+
self, document_store: WeaviateDocumentStore, caplog: pytest.LogCaptureFixture
|
|
583
|
+
) -> None:
|
|
584
|
+
"""Test that the deletion is not complete if the batch size exceeds the QUERY_MAXIMUM_RESULTS."""
|
|
585
|
+
# assume QUERY_MAXIMUM_RESULTS == 10000 with standard deployment
|
|
586
|
+
docs = [Document(content=str(i)) for i in range(0, 10005)]
|
|
587
|
+
assert await document_store.write_documents_async(docs) == 10005
|
|
588
|
+
with caplog.at_level(logging.WARNING):
|
|
589
|
+
await document_store.delete_all_documents_async(batch_size=20000)
|
|
590
|
+
assert await document_store.count_documents_async() == 5
|
|
591
|
+
assert "Not all documents have been deleted." in caplog.text
|
|
@@ -17,7 +17,7 @@ def test_init_default():
|
|
|
17
17
|
assert retriever._document_store == mock_document_store
|
|
18
18
|
assert retriever._filters == {}
|
|
19
19
|
assert retriever._top_k == 10
|
|
20
|
-
assert retriever._alpha
|
|
20
|
+
assert retriever._alpha == 0.7
|
|
21
21
|
assert retriever._max_vector_distance is None
|
|
22
22
|
assert retriever._filter_policy == FilterPolicy.REPLACE
|
|
23
23
|
|
|
@@ -56,7 +56,7 @@ def test_to_dict(_mock_weaviate):
|
|
|
56
56
|
"init_parameters": {
|
|
57
57
|
"filters": {},
|
|
58
58
|
"top_k": 10,
|
|
59
|
-
"alpha":
|
|
59
|
+
"alpha": 0.7,
|
|
60
60
|
"max_vector_distance": None,
|
|
61
61
|
"filter_policy": "replace",
|
|
62
62
|
"document_store": {
|
|
@@ -112,7 +112,7 @@ def test_from_dict(_mock_weaviate):
|
|
|
112
112
|
"init_parameters": {
|
|
113
113
|
"filters": {},
|
|
114
114
|
"top_k": 10,
|
|
115
|
-
"alpha":
|
|
115
|
+
"alpha": 0.7,
|
|
116
116
|
"max_vector_distance": None,
|
|
117
117
|
"filter_policy": "replace",
|
|
118
118
|
"document_store": {
|
|
@@ -142,7 +142,7 @@ def test_from_dict(_mock_weaviate):
|
|
|
142
142
|
assert retriever._document_store
|
|
143
143
|
assert retriever._filters == {}
|
|
144
144
|
assert retriever._top_k == 10
|
|
145
|
-
assert retriever._alpha
|
|
145
|
+
assert retriever._alpha == 0.7
|
|
146
146
|
assert retriever._max_vector_distance is None
|
|
147
147
|
|
|
148
148
|
|
|
@@ -200,7 +200,12 @@ def test_run_basic():
|
|
|
200
200
|
assert "documents" in result
|
|
201
201
|
assert len(result["documents"]) == 1
|
|
202
202
|
mock_document_store._hybrid_retrieval.assert_called_once_with(
|
|
203
|
-
query="test query",
|
|
203
|
+
query="test query",
|
|
204
|
+
query_embedding=[0.1, 0.2, 0.3],
|
|
205
|
+
filters={},
|
|
206
|
+
top_k=10,
|
|
207
|
+
alpha=0.7,
|
|
208
|
+
max_vector_distance=None,
|
|
204
209
|
)
|
|
205
210
|
|
|
206
211
|
|
|
@@ -217,7 +222,7 @@ def test_run_with_runtime_filters():
|
|
|
217
222
|
query_embedding=[0.1, 0.2, 0.3],
|
|
218
223
|
filters={"runtime": "filter"},
|
|
219
224
|
top_k=10,
|
|
220
|
-
alpha=
|
|
225
|
+
alpha=0.7,
|
|
221
226
|
max_vector_distance=None,
|
|
222
227
|
)
|
|
223
228
|
|
|
@@ -259,7 +264,7 @@ def test_run_empty_query():
|
|
|
259
264
|
assert "documents" in result
|
|
260
265
|
assert len(result["documents"]) == 0
|
|
261
266
|
mock_document_store._hybrid_retrieval.assert_called_once_with(
|
|
262
|
-
query="", query_embedding=[0.1, 0.2, 0.3], filters={}, top_k=10, alpha=
|
|
267
|
+
query="", query_embedding=[0.1, 0.2, 0.3], filters={}, top_k=10, alpha=0.7, max_vector_distance=None
|
|
263
268
|
)
|
|
264
269
|
|
|
265
270
|
|
|
@@ -288,7 +293,7 @@ def test_from_dict_no_filter_policy(_mock_weaviate):
|
|
|
288
293
|
"init_parameters": {
|
|
289
294
|
"filters": {},
|
|
290
295
|
"top_k": 10,
|
|
291
|
-
"alpha":
|
|
296
|
+
"alpha": 0.7,
|
|
292
297
|
"max_vector_distance": None,
|
|
293
298
|
# filter_policy intentionally omitted
|
|
294
299
|
"document_store": {
|
|
@@ -318,7 +323,7 @@ def test_from_dict_no_filter_policy(_mock_weaviate):
|
|
|
318
323
|
assert retriever._document_store
|
|
319
324
|
assert retriever._filters == {}
|
|
320
325
|
assert retriever._top_k == 10
|
|
321
|
-
assert retriever._alpha
|
|
326
|
+
assert retriever._alpha == 0.7
|
|
322
327
|
assert retriever._max_vector_distance is None
|
|
323
328
|
assert retriever._filter_policy == FilterPolicy.REPLACE
|
|
324
329
|
|
|
@@ -381,7 +386,7 @@ def test_run_with_max_vector_distance_zero_runtime():
|
|
|
381
386
|
query_embedding=[0.1, 0.2],
|
|
382
387
|
filters={},
|
|
383
388
|
top_k=10,
|
|
384
|
-
alpha=
|
|
389
|
+
alpha=0.7,
|
|
385
390
|
max_vector_distance=0.0,
|
|
386
391
|
)
|
|
387
392
|
|
|
@@ -402,7 +407,7 @@ def test_run_with_max_vector_distance_zero_init_and_none_runtime():
|
|
|
402
407
|
query_embedding=[0.1, 0.2],
|
|
403
408
|
filters={},
|
|
404
409
|
top_k=10,
|
|
405
|
-
alpha=
|
|
410
|
+
alpha=0.7,
|
|
406
411
|
max_vector_distance=0.0,
|
|
407
412
|
)
|
|
408
413
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|