qdrant-haystack 9.2.0__tar.gz → 9.3.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.
Files changed (29) hide show
  1. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/CHANGELOG.md +11 -1
  2. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/PKG-INFO +6 -21
  3. qdrant_haystack-9.3.0/README.md +13 -0
  4. qdrant_haystack-9.3.0/pydoc/config_docusaurus.yml +30 -0
  5. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/pyproject.toml +1 -5
  6. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/qdrant/document_store.py +118 -9
  7. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/test_document_store.py +38 -0
  8. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/test_document_store_async.py +40 -0
  9. qdrant_haystack-9.2.0/README.md +0 -28
  10. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/.gitignore +0 -0
  11. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/LICENSE.txt +0 -0
  12. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/examples/embedding_retrieval.py +0 -0
  13. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/pydoc/config.yml +0 -0
  14. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
  15. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/src/haystack_integrations/components/retrievers/qdrant/__init__.py +0 -0
  16. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/src/haystack_integrations/components/retrievers/qdrant/retriever.py +0 -0
  17. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/py.typed +0 -0
  18. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/qdrant/__init__.py +0 -0
  19. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/qdrant/converters.py +0 -0
  20. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/qdrant/filters.py +0 -0
  21. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/qdrant/migrate_to_sparse.py +0 -0
  22. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/__init__.py +0 -0
  23. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/conftest.py +0 -0
  24. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/test_converters.py +0 -0
  25. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/test_dict_converters.py +0 -0
  26. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/test_embedding_retriever.py +0 -0
  27. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/test_filters.py +0 -0
  28. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/test_hybrid_retriever.py +0 -0
  29. {qdrant_haystack-9.2.0 → qdrant_haystack-9.3.0}/tests/test_sparse_embedding_retriever.py +0 -0
@@ -1,6 +1,16 @@
1
1
  # Changelog
2
2
 
3
- ## [integrations/qdrant-v9.1.3] - 2025-05-28
3
+ ## [integrations/qdrant-v9.2.0] - 2025-06-12
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - Fix Qdrant types + add py.typed (#1919)
8
+
9
+
10
+ ### 🧹 Chores
11
+
12
+ - Align core-integrations Hatch scripts (#1898)
13
+ - Update md files for new hatch scripts (#1911)
4
14
 
5
15
  ### 🌀 Miscellaneous
6
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qdrant-haystack
3
- Version: 9.2.0
3
+ Version: 9.3.0
4
4
  Summary: An integration of Qdrant ANN vector database backend 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/qdrant/README.md
@@ -28,26 +28,11 @@ Description-Content-Type: text/markdown
28
28
  [![PyPI - Version](https://img.shields.io/pypi/v/qdrant-haystack.svg)](https://pypi.org/project/qdrant-haystack)
29
29
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/qdrant-haystack.svg)](https://pypi.org/project/qdrant-haystack)
30
30
 
31
- -----
31
+ - [Integration page](https://haystack.deepset.ai/integrations/qdrant-document-store)
32
+ - [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/qdrant/CHANGELOG.md)
32
33
 
33
- **Table of Contents**
34
+ ---
34
35
 
35
- - [Installation](#installation)
36
- - [License](#license)
36
+ ## Contributing
37
37
 
38
- ## Installation
39
-
40
- ```console
41
- pip install qdrant-haystack
42
- ```
43
-
44
- ## Testing
45
- The test suites use Qdrant's in-memory instance. No additional steps required.
46
-
47
- ```console
48
- hatch run test:all
49
- ```
50
-
51
- ## License
52
-
53
- `qdrant-haystack` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
38
+ Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
@@ -0,0 +1,13 @@
1
+ # qdrant-haystack
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/qdrant-haystack.svg)](https://pypi.org/project/qdrant-haystack)
4
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/qdrant-haystack.svg)](https://pypi.org/project/qdrant-haystack)
5
+
6
+ - [Integration page](https://haystack.deepset.ai/integrations/qdrant-document-store)
7
+ - [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/qdrant/CHANGELOG.md)
8
+
9
+ ---
10
+
11
+ ## Contributing
12
+
13
+ Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
@@ -0,0 +1,30 @@
1
+ loaders:
2
+ - ignore_when_discovered:
3
+ - __init__
4
+ modules:
5
+ - haystack_integrations.components.retrievers.qdrant.retriever
6
+ - haystack_integrations.document_stores.qdrant.document_store
7
+ - haystack_integrations.document_stores.qdrant.migrate_to_sparse
8
+ search_path:
9
+ - ../src
10
+ type: haystack_pydoc_tools.loaders.CustomPythonLoader
11
+ processors:
12
+ - do_not_filter_modules: false
13
+ documented_only: true
14
+ expression: null
15
+ skip_empty_modules: true
16
+ type: filter
17
+ - type: smart
18
+ - type: crossref
19
+ renderer:
20
+ description: Qdrant integration for Haystack
21
+ id: integrations-qdrant
22
+ markdown:
23
+ add_member_class_prefix: false
24
+ add_method_class_prefix: true
25
+ classdef_code_block: false
26
+ descriptive_class_title: false
27
+ descriptive_module_title: true
28
+ filename: qdrant.md
29
+ title: Qdrant
30
+ type: haystack_pydoc_tools.renderers.DocusaurusRenderer
@@ -67,7 +67,7 @@ dependencies = [
67
67
  unit = 'pytest -m "not integration" {args:tests}'
68
68
  integration = 'pytest -m "integration" {args:tests}'
69
69
  all = 'pytest {args:tests}'
70
- cov-retry = 'all --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x'
70
+ cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x {args:tests}'
71
71
 
72
72
  types = """mypy -p haystack_integrations.document_stores.qdrant \
73
73
  -p haystack_integrations.components.retrievers.qdrant {args}"""
@@ -78,10 +78,6 @@ non_interactive = true
78
78
  check_untyped_defs = true
79
79
  disallow_incomplete_defs = true
80
80
 
81
- [tool.black]
82
- target-version = ["py38"]
83
- line-length = 120
84
- skip-string-normalization = true
85
81
 
86
82
  [tool.ruff]
87
83
  target-version = "py38"
@@ -53,9 +53,8 @@ def get_batches_from_generator(iterable: List, n: int) -> Generator:
53
53
 
54
54
  class QdrantDocumentStore:
55
55
  """
56
- A QdrantDocumentStore implementation that you
57
- can use with any Qdrant instance: in-memory, disk-persisted, Docker-based,
58
- and Qdrant Cloud Cluster deployments.
56
+ A QdrantDocumentStore implementation that you can use with any Qdrant instance: in-memory, disk-persisted,
57
+ Docker-based, and Qdrant Cloud Cluster deployments.
59
58
 
60
59
  Usage example by creating an in-memory instance:
61
60
 
@@ -65,7 +64,8 @@ class QdrantDocumentStore:
65
64
 
66
65
  document_store = QdrantDocumentStore(
67
66
  ":memory:",
68
- recreate_index=True
67
+ recreate_index=True,
68
+ embedding_dim=5
69
69
  )
70
70
  document_store.write_documents([
71
71
  Document(content="This is first", embedding=[0.0]*5),
@@ -135,6 +135,8 @@ class QdrantDocumentStore:
135
135
  payload_fields_to_index: Optional[List[dict]] = None,
136
136
  ) -> None:
137
137
  """
138
+ Initializes a QdrantDocumentStore.
139
+
138
140
  :param location:
139
141
  If `":memory:"` - use in-memory Qdrant instance.
140
142
  If `str` - use it as a URL parameter.
@@ -350,7 +352,7 @@ class QdrantDocumentStore:
350
352
  # No need to initialize client here as _get_documents_generator
351
353
  # will handle client initialization internally
352
354
 
353
- self._validate_filters(filters)
355
+ QdrantDocumentStore._validate_filters(filters)
354
356
  return list(
355
357
  self._get_documents_generator(
356
358
  filters,
@@ -367,7 +369,7 @@ class QdrantDocumentStore:
367
369
  # No need to initialize client here as _get_documents_generator_async
368
370
  # will handle client initialization internally
369
371
 
370
- self._validate_filters(filters)
372
+ QdrantDocumentStore._validate_filters(filters)
371
373
  return [doc async for doc in self._get_documents_generator_async(filters)]
372
374
 
373
375
  def write_documents(
@@ -521,6 +523,108 @@ class QdrantDocumentStore:
521
523
  "Called QdrantDocumentStore.delete_documents_async() on a non-existing ID",
522
524
  )
523
525
 
526
+ def delete_all_documents(self, recreate_index: bool = False) -> None:
527
+ """
528
+ Deletes all documents from the document store.
529
+
530
+ :param recreate_index: Whether to recreate the index after deleting all documents.
531
+ """
532
+
533
+ self._initialize_client()
534
+ assert self._client is not None
535
+
536
+ if recreate_index:
537
+ # get current collection config as json
538
+ collection_info = self._client.get_collection(collection_name=self.index)
539
+ info_json = collection_info.model_dump()
540
+
541
+ # deal with the Optional use_sparse_embeddings
542
+ sparse_vectors = info_json["config"]["params"]["sparse_vectors"]
543
+ use_sparse_embeddings = True if sparse_vectors else False
544
+
545
+ # deal with the Optional sparse_idf
546
+ hnsw_config = info_json["config"]["params"]["vectors"].get("config", {}).get("hnsw_config", None)
547
+ sparse_idf = True if use_sparse_embeddings and hnsw_config else False
548
+
549
+ # recreate collection
550
+ self._set_up_collection(
551
+ collection_name=self.index,
552
+ embedding_dim=info_json["config"]["params"]["vectors"]["size"],
553
+ recreate_collection=True,
554
+ similarity=info_json["config"]["params"]["vectors"]["distance"].lower(),
555
+ use_sparse_embeddings=use_sparse_embeddings,
556
+ sparse_idf=sparse_idf,
557
+ on_disk=info_json["config"]["hnsw_config"]["on_disk"],
558
+ payload_fields_to_index=info_json["payload_schema"],
559
+ )
560
+
561
+ else:
562
+ try:
563
+ self._client.delete(
564
+ collection_name=self.index,
565
+ points_selector=rest.FilterSelector(
566
+ filter=rest.Filter(
567
+ must=[],
568
+ )
569
+ ),
570
+ wait=self.wait_result_from_api,
571
+ )
572
+ except Exception as e:
573
+ logger.warning(
574
+ f"Error {e} when calling QdrantDocumentStore.delete_all_documents()",
575
+ )
576
+
577
+ async def delete_all_documents_async(self, recreate_index: bool = False) -> None:
578
+ """
579
+ Asynchronously deletes all documents from the document store.
580
+
581
+ :param recreate_index: Whether to recreate the index after deleting all documents.
582
+ """
583
+
584
+ await self._initialize_async_client()
585
+ assert self._async_client is not None
586
+
587
+ if recreate_index:
588
+ # get current collection config as json
589
+ collection_info = await self._async_client.get_collection(collection_name=self.index)
590
+ info_json = collection_info.model_dump()
591
+
592
+ # deal with the Optional use_sparse_embeddings
593
+ sparse_vectors = info_json["config"]["params"]["sparse_vectors"]
594
+ use_sparse_embeddings = True if sparse_vectors else False
595
+
596
+ # deal with the Optional sparse_idf
597
+ hnsw_config = info_json["config"]["params"]["vectors"].get("config", {}).get("hnsw_config", None)
598
+ sparse_idf = True if use_sparse_embeddings and hnsw_config else False
599
+
600
+ # recreate collection
601
+ await self._set_up_collection_async(
602
+ collection_name=self.index,
603
+ embedding_dim=info_json["config"]["params"]["vectors"]["size"],
604
+ recreate_collection=True,
605
+ similarity=info_json["config"]["params"]["vectors"]["distance"].lower(),
606
+ use_sparse_embeddings=use_sparse_embeddings,
607
+ sparse_idf=sparse_idf,
608
+ on_disk=info_json["config"]["hnsw_config"]["on_disk"],
609
+ payload_fields_to_index=info_json["payload_schema"],
610
+ )
611
+
612
+ else:
613
+ try:
614
+ await self._async_client.delete(
615
+ collection_name=self.index,
616
+ points_selector=rest.FilterSelector(
617
+ filter=rest.Filter(
618
+ must=[],
619
+ )
620
+ ),
621
+ wait=self.wait_result_from_api,
622
+ )
623
+ except Exception as e:
624
+ logger.warning(
625
+ f"Error {e} when calling QdrantDocumentStore.delete_all_documents_async()",
626
+ )
627
+
524
628
  @classmethod
525
629
  def from_dict(cls, data: Dict[str, Any]) -> "QdrantDocumentStore":
526
630
  """
@@ -1214,7 +1318,8 @@ class QdrantDocumentStore:
1214
1318
 
1215
1319
  def _create_payload_index(self, collection_name: str, payload_fields_to_index: Optional[List[dict]] = None) -> None:
1216
1320
  """
1217
- Create payload index for the collection if payload_fields_to_index is provided
1321
+ Create payload index for the collection if payload_fields_to_index is provided.
1322
+
1218
1323
  See: https://qdrant.tech/documentation/concepts/indexing/#payload-index
1219
1324
  """
1220
1325
  if payload_fields_to_index is not None:
@@ -1233,7 +1338,8 @@ class QdrantDocumentStore:
1233
1338
  self, collection_name: str, payload_fields_to_index: Optional[List[dict]] = None
1234
1339
  ) -> None:
1235
1340
  """
1236
- Asynchronously create payload index for the collection if payload_fields_to_index is provided
1341
+ Asynchronously create payload index for the collection if payload_fields_to_index is provided.
1342
+
1237
1343
  See: https://qdrant.tech/documentation/concepts/indexing/#payload-index
1238
1344
  """
1239
1345
  if payload_fields_to_index is not None:
@@ -1261,6 +1367,7 @@ class QdrantDocumentStore:
1261
1367
  ) -> None:
1262
1368
  """
1263
1369
  Sets up the Qdrant collection with the specified parameters.
1370
+
1264
1371
  :param collection_name:
1265
1372
  The name of the collection to set up.
1266
1373
  :param embedding_dim:
@@ -1317,6 +1424,7 @@ class QdrantDocumentStore:
1317
1424
  ) -> None:
1318
1425
  """
1319
1426
  Asynchronously sets up the Qdrant collection with the specified parameters.
1427
+
1320
1428
  :param collection_name:
1321
1429
  The name of the collection to set up.
1322
1430
  :param embedding_dim:
@@ -1601,7 +1709,8 @@ class QdrantDocumentStore:
1601
1709
 
1602
1710
  return vectors_config, sparse_vectors_config
1603
1711
 
1604
- def _validate_filters(self, filters: Optional[Union[Dict[str, Any], rest.Filter]] = None) -> None:
1712
+ @staticmethod
1713
+ def _validate_filters(filters: Optional[Union[Dict[str, Any], rest.Filter]] = None) -> None:
1605
1714
  """
1606
1715
  Validates the filters provided for querying.
1607
1716
 
@@ -297,3 +297,41 @@ class TestQdrantDocumentStore(CountDocumentsTest, WriteDocumentsTest, DeleteDocu
297
297
  ):
298
298
  with pytest.raises(ValueError, match="different vector size"):
299
299
  document_store._set_up_collection("test_collection", 768, False, "cosine", False, False)
300
+
301
+ def test_delete_all_documents_no_index_recreation(self, document_store):
302
+ document_store._initialize_client()
303
+
304
+ # write some documents
305
+ docs = [Document(id=str(i)) for i in range(5)]
306
+ document_store.write_documents(docs)
307
+
308
+ # delete all documents without recreating the index
309
+ document_store.delete_all_documents(recreate_index=False)
310
+ assert document_store.count_documents() == 0
311
+
312
+ # ensure the collection still exists by writing documents again
313
+ document_store.write_documents(docs)
314
+ assert document_store.count_documents() == 5
315
+
316
+ def test_delete_all_documents_index_recreation(self, document_store):
317
+ document_store._initialize_client()
318
+
319
+ # write some documents
320
+ docs = [Document(id=str(i)) for i in range(5)]
321
+ document_store.write_documents(docs)
322
+
323
+ # get the current document_store config
324
+ config_before = document_store._client.get_collection(document_store.index)
325
+
326
+ # delete all documents with recreating the index
327
+ document_store.delete_all_documents(recreate_index=True)
328
+ assert document_store.count_documents() == 0
329
+
330
+ # assure that with the same config
331
+ config_after = document_store._client.get_collection(document_store.index)
332
+
333
+ assert config_before.config == config_after.config
334
+
335
+ # ensure the collection still exists by writing documents again
336
+ document_store.write_documents(docs)
337
+ assert document_store.count_documents() == 5
@@ -218,3 +218,43 @@ class TestQdrantDocumentStore:
218
218
  ):
219
219
  with pytest.raises(ValueError, match="different similarity"):
220
220
  await document_store._set_up_collection_async("test_collection", 768, False, "cosine", False, False)
221
+
222
+ @pytest.mark.asyncio
223
+ async def test_delete_all_documents_async_no_index_recreation(self, document_store):
224
+ await document_store._initialize_async_client()
225
+
226
+ # write some documents
227
+ docs = [Document(id=str(i)) for i in range(5)]
228
+ await document_store.write_documents_async(docs)
229
+
230
+ # delete all documents without recreating the index
231
+ await document_store.delete_all_documents_async(recreate_index=False)
232
+ assert await document_store.count_documents_async() == 0
233
+
234
+ # ensure the collection still exists by writing documents again
235
+ await document_store.write_documents_async(docs)
236
+ assert await document_store.count_documents_async() == 5
237
+
238
+ @pytest.mark.asyncio
239
+ async def test_delete_all_documents_async_index_recreation(self, document_store):
240
+ await document_store._initialize_async_client()
241
+
242
+ # write some documents
243
+ docs = [Document(id=str(i)) for i in range(5)]
244
+ await document_store.write_documents_async(docs)
245
+
246
+ # get the current document_store config
247
+ config_before = await document_store._async_client.get_collection(document_store.index)
248
+
249
+ # delete all documents with recreating the index
250
+ await document_store.delete_all_documents_async(recreate_index=True)
251
+ assert await document_store.count_documents_async() == 0
252
+
253
+ # assure that with the same config
254
+ config_after = await document_store._async_client.get_collection(document_store.index)
255
+
256
+ assert config_before.config == config_after.config
257
+
258
+ # ensure the collection still exists by writing documents again
259
+ await document_store.write_documents_async(docs)
260
+ assert await document_store.count_documents_async() == 5
@@ -1,28 +0,0 @@
1
- # qdrant-haystack
2
-
3
- [![PyPI - Version](https://img.shields.io/pypi/v/qdrant-haystack.svg)](https://pypi.org/project/qdrant-haystack)
4
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/qdrant-haystack.svg)](https://pypi.org/project/qdrant-haystack)
5
-
6
- -----
7
-
8
- **Table of Contents**
9
-
10
- - [Installation](#installation)
11
- - [License](#license)
12
-
13
- ## Installation
14
-
15
- ```console
16
- pip install qdrant-haystack
17
- ```
18
-
19
- ## Testing
20
- The test suites use Qdrant's in-memory instance. No additional steps required.
21
-
22
- ```console
23
- hatch run test:all
24
- ```
25
-
26
- ## License
27
-
28
- `qdrant-haystack` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.