qdrant-haystack 9.1.3__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 (30) hide show
  1. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/CHANGELOG.md +16 -0
  2. {qdrant_haystack-9.1.3 → 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.1.3 → qdrant_haystack-9.3.0}/pyproject.toml +34 -34
  6. qdrant_haystack-9.3.0/src/haystack_integrations/components/retrievers/py.typed +0 -0
  7. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/src/haystack_integrations/components/retrievers/qdrant/retriever.py +77 -6
  8. qdrant_haystack-9.3.0/src/haystack_integrations/document_stores/py.typed +0 -0
  9. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/qdrant/converters.py +10 -8
  10. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/qdrant/document_store.py +184 -50
  11. qdrant_haystack-9.3.0/src/haystack_integrations/document_stores/qdrant/filters.py +234 -0
  12. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/qdrant/migrate_to_sparse.py +11 -7
  13. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/test_converters.py +0 -2
  14. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/test_document_store.py +38 -7
  15. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/test_document_store_async.py +40 -4
  16. qdrant_haystack-9.1.3/README.md +0 -28
  17. qdrant_haystack-9.1.3/src/haystack_integrations/document_stores/qdrant/filters.py +0 -316
  18. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/.gitignore +0 -0
  19. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/LICENSE.txt +0 -0
  20. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/examples/embedding_retrieval.py +0 -0
  21. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/pydoc/config.yml +0 -0
  22. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/src/haystack_integrations/components/retrievers/qdrant/__init__.py +0 -0
  23. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/src/haystack_integrations/document_stores/qdrant/__init__.py +0 -0
  24. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/__init__.py +0 -0
  25. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/conftest.py +0 -0
  26. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/test_dict_converters.py +0 -0
  27. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/test_embedding_retriever.py +0 -0
  28. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/test_filters.py +0 -0
  29. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/test_hybrid_retriever.py +0 -0
  30. {qdrant_haystack-9.1.3 → qdrant_haystack-9.3.0}/tests/test_sparse_embedding_retriever.py +0 -0
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
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)
14
+
15
+ ### 🌀 Miscellaneous
16
+
17
+ - Add pins for Qdrant (#1853)
18
+
3
19
  ## [integrations/qdrant-v9.1.2] - 2025-05-27
4
20
 
5
21
  ### 🐛 Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qdrant-haystack
3
- Version: 9.1.3
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
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
@@ -46,30 +46,38 @@ git_describe_command = 'git describe --tags --match="integrations/qdrant-v[0-9]*
46
46
 
47
47
  [tool.hatch.envs.default]
48
48
  installer = "uv"
49
- dependencies = ["coverage[toml]>=6.5", "pytest", "pytest-rerunfailures", "haystack-pydoc-tools", "pytest-asyncio"]
49
+ dependencies = ["haystack-pydoc-tools", "ruff"]
50
+
50
51
  [tool.hatch.envs.default.scripts]
51
- test = "pytest {args:tests}"
52
- test-cov = "coverage run -m pytest {args:tests}"
53
- test-cov-retry = "test-cov --reruns 3 --reruns-delay 30 -x"
54
- cov-report = ["- coverage combine", "coverage report"]
55
- cov = ["test-cov", "cov-report"]
56
- cov-retry = ["test-cov-retry", "cov-report"]
57
52
  docs = ["pydoc-markdown pydoc/config.yml"]
53
+ fmt = "ruff check --fix {args} && ruff format {args}"
54
+ fmt-check = "ruff check {args} && ruff format --check {args}"
55
+
56
+ [tool.hatch.envs.test]
57
+ dependencies = [
58
+ "pytest",
59
+ "pytest-asyncio",
60
+ "pytest-cov",
61
+ "pytest-rerunfailures",
62
+ "mypy",
63
+ "pip"
64
+ ]
65
+
66
+ [tool.hatch.envs.test.scripts]
67
+ unit = 'pytest -m "not integration" {args:tests}'
68
+ integration = 'pytest -m "integration" {args:tests}'
69
+ all = 'pytest {args:tests}'
70
+ cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x {args:tests}'
71
+
72
+ types = """mypy -p haystack_integrations.document_stores.qdrant \
73
+ -p haystack_integrations.components.retrievers.qdrant {args}"""
74
+
75
+ [tool.mypy]
76
+ install_types = true
77
+ non_interactive = true
78
+ check_untyped_defs = true
79
+ disallow_incomplete_defs = true
58
80
 
59
- [tool.hatch.envs.lint]
60
- installer = "uv"
61
- detached = true
62
- dependencies = ["pip", "black>=23.1.0", "mypy>=1.0.0", "ruff>=0.0.243"]
63
- [tool.hatch.envs.lint.scripts]
64
- typing = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
65
- style = ["ruff check {args:.}", "black --check --diff {args:.}"]
66
- fmt = ["black {args:.}", "ruff check --fix {args:.}", "style"]
67
- all = ["style", "typing"]
68
-
69
- [tool.black]
70
- target-version = ["py38"]
71
- line-length = 120
72
- skip-string-normalization = true
73
81
 
74
82
  [tool.ruff]
75
83
  target-version = "py38"
@@ -146,18 +154,10 @@ parallel = false
146
154
 
147
155
 
148
156
  [tool.coverage.report]
149
- omit = ["*/tests/*", "*/__init__.py"]
157
+ omit = [
158
+ "*/tests/*",
159
+ "*/__init__.py",
160
+ "src/haystack_integrations/document_stores/qdrant/migrate_to_sparse.py",
161
+ ]
150
162
  show_missing = true
151
163
  exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
152
-
153
-
154
- [[tool.mypy.overrides]]
155
- module = [
156
- "haystack.*",
157
- "haystack_integrations.*",
158
- "pytest.*",
159
- "qdrant_client.*",
160
- "numpy",
161
- "grpc",
162
- ]
163
- ignore_missing_imports = true
@@ -8,6 +8,11 @@ from qdrant_client.http import models
8
8
 
9
9
  from haystack_integrations.document_stores.qdrant import QdrantDocumentStore
10
10
 
11
+ FILTER_POLICY_MERGE_ERROR_MESSAGE = (
12
+ "Native Qdrant filters cannot be used with filter_policy set to MERGE. "
13
+ "Set filter_policy to REPLACE or use Haystack filters instead."
14
+ )
15
+
11
16
 
12
17
  @component
13
18
  class QdrantEmbeddingRetriever:
@@ -153,8 +158,19 @@ class QdrantEmbeddingRetriever:
153
158
  :returns:
154
159
  The retrieved documents.
155
160
 
161
+ :raises ValueError: If 'filter_policy' is set to 'MERGE' and 'filters' is a native Qdrant filter.
156
162
  """
157
- filters = apply_filter_policy(self._filter_policy, self._filters, filters)
163
+ if self._filter_policy == FilterPolicy.MERGE and (
164
+ isinstance(self._filters, models.Filter) or isinstance(filters, models.Filter)
165
+ ):
166
+ raise ValueError(FILTER_POLICY_MERGE_ERROR_MESSAGE)
167
+
168
+ # Replacing filters works with native Qdrant filters even if the type is wrong
169
+ filters = apply_filter_policy(
170
+ filter_policy=self._filter_policy,
171
+ init_filters=self._filters, # type: ignore[arg-type]
172
+ runtime_filters=filters, # type: ignore[arg-type]
173
+ )
158
174
 
159
175
  docs = self._document_store._query_by_embedding(
160
176
  query_embedding=query_embedding,
@@ -197,8 +213,19 @@ class QdrantEmbeddingRetriever:
197
213
  :returns:
198
214
  The retrieved documents.
199
215
 
216
+ :raises ValueError: If 'filter_policy' is set to 'MERGE' and 'filters' is a native Qdrant filter.
200
217
  """
201
- filters = apply_filter_policy(self._filter_policy, self._filters, filters)
218
+ if self._filter_policy == FilterPolicy.MERGE and (
219
+ isinstance(self._filters, models.Filter) or isinstance(filters, models.Filter)
220
+ ):
221
+ raise ValueError(FILTER_POLICY_MERGE_ERROR_MESSAGE)
222
+
223
+ # Replacing filters works with native Qdrant filters even if the type is wrong
224
+ filters = apply_filter_policy(
225
+ filter_policy=self._filter_policy,
226
+ init_filters=self._filters, # type: ignore[arg-type]
227
+ runtime_filters=filters, # type: ignore[arg-type]
228
+ )
202
229
 
203
230
  docs = await self._document_store._query_by_embedding_async(
204
231
  query_embedding=query_embedding,
@@ -364,8 +391,19 @@ class QdrantSparseEmbeddingRetriever:
364
391
  :returns:
365
392
  The retrieved documents.
366
393
 
394
+ :raises ValueError: If 'filter_policy' is set to 'MERGE' and 'filters' is a native Qdrant filter.
367
395
  """
368
- filters = apply_filter_policy(self._filter_policy, self._filters, filters)
396
+ if self._filter_policy == FilterPolicy.MERGE and (
397
+ isinstance(self._filters, models.Filter) or isinstance(filters, models.Filter)
398
+ ):
399
+ raise ValueError(FILTER_POLICY_MERGE_ERROR_MESSAGE)
400
+
401
+ # Replacing filters works with native Qdrant filters even if the type is wrong
402
+ filters = apply_filter_policy(
403
+ filter_policy=self._filter_policy,
404
+ init_filters=self._filters, # type: ignore[arg-type]
405
+ runtime_filters=filters, # type: ignore[arg-type]
406
+ )
369
407
 
370
408
  docs = self._document_store._query_by_sparse(
371
409
  query_sparse_embedding=query_sparse_embedding,
@@ -413,8 +451,19 @@ class QdrantSparseEmbeddingRetriever:
413
451
  :returns:
414
452
  The retrieved documents.
415
453
 
454
+ :raises ValueError: If 'filter_policy' is set to 'MERGE' and 'filters' is a native Qdrant filter.
416
455
  """
417
- filters = apply_filter_policy(self._filter_policy, self._filters, filters)
456
+ if self._filter_policy == FilterPolicy.MERGE and (
457
+ isinstance(self._filters, models.Filter) or isinstance(filters, models.Filter)
458
+ ):
459
+ raise ValueError(FILTER_POLICY_MERGE_ERROR_MESSAGE)
460
+
461
+ # Replacing filters works with native Qdrant filters even if the type is wrong
462
+ filters = apply_filter_policy(
463
+ filter_policy=self._filter_policy,
464
+ init_filters=self._filters, # type: ignore[arg-type]
465
+ runtime_filters=filters, # type: ignore[arg-type]
466
+ )
418
467
 
419
468
  docs = await self._document_store._query_by_sparse_async(
420
469
  query_sparse_embedding=query_sparse_embedding,
@@ -579,8 +628,19 @@ class QdrantHybridRetriever:
579
628
  :returns:
580
629
  The retrieved documents.
581
630
 
631
+ :raises ValueError: If 'filter_policy' is set to 'MERGE' and 'filters' is a native Qdrant filter.
582
632
  """
583
- filters = apply_filter_policy(self._filter_policy, self._filters, filters)
633
+ if self._filter_policy == FilterPolicy.MERGE and (
634
+ isinstance(self._filters, models.Filter) or isinstance(filters, models.Filter)
635
+ ):
636
+ raise ValueError(FILTER_POLICY_MERGE_ERROR_MESSAGE)
637
+
638
+ # Replacing filters works with native Qdrant filters even if the type is wrong
639
+ filters = apply_filter_policy(
640
+ filter_policy=self._filter_policy,
641
+ init_filters=self._filters, # type: ignore[arg-type]
642
+ runtime_filters=filters, # type: ignore[arg-type]
643
+ )
584
644
 
585
645
  docs = self._document_store._query_hybrid(
586
646
  query_embedding=query_embedding,
@@ -628,8 +688,19 @@ class QdrantHybridRetriever:
628
688
  :returns:
629
689
  The retrieved documents.
630
690
 
691
+ :raises ValueError: If 'filter_policy' is set to 'MERGE' and 'filters' is a native Qdrant filter.
631
692
  """
632
- filters = apply_filter_policy(self._filter_policy, self._filters, filters)
693
+ if self._filter_policy == FilterPolicy.MERGE and (
694
+ isinstance(self._filters, models.Filter) or isinstance(filters, models.Filter)
695
+ ):
696
+ raise ValueError(FILTER_POLICY_MERGE_ERROR_MESSAGE)
697
+
698
+ # Replacing filters works with native Qdrant filters even if the type is wrong
699
+ filters = apply_filter_policy(
700
+ filter_policy=self._filter_policy,
701
+ init_filters=self._filters, # type: ignore[arg-type]
702
+ runtime_filters=filters, # type: ignore[arg-type]
703
+ )
633
704
 
634
705
  docs = await self._document_store._query_hybrid_async(
635
706
  query_embedding=query_embedding,
@@ -37,7 +37,7 @@ def convert_haystack_documents_to_qdrant_points(
37
37
 
38
38
  else:
39
39
  vector = payload.pop("embedding") or {}
40
- _id = convert_id(payload.get("id"))
40
+ _id = convert_id(document.id)
41
41
 
42
42
  point = rest.PointStruct(
43
43
  payload=payload,
@@ -62,19 +62,21 @@ QdrantPoint = Union[rest.ScoredPoint, rest.Record]
62
62
 
63
63
 
64
64
  def convert_qdrant_point_to_haystack_document(point: QdrantPoint, use_sparse_embeddings: bool) -> Document:
65
- payload = {**point.payload}
65
+ payload = point.payload or {}
66
66
  payload["score"] = point.score if hasattr(point, "score") else None
67
67
 
68
68
  if not use_sparse_embeddings:
69
69
  payload["embedding"] = point.vector if hasattr(point, "vector") else None
70
- elif hasattr(point, "vector") and point.vector is not None:
70
+ elif hasattr(point, "vector") and point.vector is not None and isinstance(point.vector, dict):
71
71
  payload["embedding"] = point.vector.get(DENSE_VECTORS_NAME)
72
72
 
73
73
  if SPARSE_VECTORS_NAME in point.vector:
74
- parse_vector_dict = {
75
- "indices": point.vector[SPARSE_VECTORS_NAME].indices,
76
- "values": point.vector[SPARSE_VECTORS_NAME].values,
77
- }
78
- payload["sparse_embedding"] = parse_vector_dict
74
+ sparse_vector = point.vector[SPARSE_VECTORS_NAME]
75
+ if isinstance(sparse_vector, rest.SparseVector):
76
+ sparse_vector_dict = {
77
+ "indices": sparse_vector.indices,
78
+ "values": sparse_vector.values,
79
+ }
80
+ payload["sparse_embedding"] = sparse_vector_dict
79
81
 
80
82
  return Document.from_dict(payload)