qdrant-haystack 11.1.0__tar.gz → 12.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/CHANGELOG.md +11 -0
  2. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/PKG-INFO +1 -1
  3. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/src/haystack_integrations/document_stores/qdrant/document_store.py +1 -2
  4. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/src/haystack_integrations/document_stores/qdrant/filters.py +7 -31
  5. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/test_filters.py +52 -10
  6. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/.gitignore +0 -0
  7. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/LICENSE.txt +0 -0
  8. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/README.md +0 -0
  9. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/examples/embedding_retrieval.py +0 -0
  10. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/pydoc/config_docusaurus.yml +0 -0
  11. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/pyproject.toml +0 -0
  12. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
  13. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/src/haystack_integrations/components/retrievers/qdrant/__init__.py +0 -0
  14. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/src/haystack_integrations/components/retrievers/qdrant/retriever.py +0 -0
  15. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/src/haystack_integrations/document_stores/py.typed +0 -0
  16. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/src/haystack_integrations/document_stores/qdrant/__init__.py +0 -0
  17. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/src/haystack_integrations/document_stores/qdrant/converters.py +0 -0
  18. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/src/haystack_integrations/document_stores/qdrant/migrate_to_sparse.py +0 -0
  19. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/__init__.py +0 -0
  20. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/conftest.py +0 -0
  21. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/test_converters.py +0 -0
  22. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/test_dict_converters.py +0 -0
  23. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/test_document_store.py +0 -0
  24. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/test_document_store_async.py +0 -0
  25. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/test_embedding_retriever.py +0 -0
  26. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/test_hybrid_retriever.py +0 -0
  27. {qdrant_haystack-11.1.0 → qdrant_haystack-12.0.0}/tests/test_sparse_embedding_retriever.py +0 -0
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/qdrant-v11.1.0] - 2026-10-01
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - *(qdrant)* Respect falsy run() overrides in retrievers (#4007)
8
+
9
+ ### 📚 Documentation
10
+
11
+ - Clarify score_threshold semantics for Qdrant hybrid retrieval (#4008)
12
+
13
+
3
14
  ## [integrations/qdrant-v11.0.0] - 2026-09-25
4
15
 
5
16
  ### 🚜 Refactor
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: qdrant-haystack
3
- Version: 11.1.0
3
+ Version: 12.0.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
@@ -11,7 +11,7 @@ from haystack.dataclasses import Document
11
11
  from haystack.dataclasses.sparse_embedding import SparseEmbedding
12
12
  from haystack.document_stores.errors import DocumentStoreError, DuplicateDocumentError
13
13
  from haystack.document_stores.types import DuplicatePolicy
14
- from haystack.utils import Secret, deserialize_secrets_inplace
14
+ from haystack.utils import Secret
15
15
  from haystack.utils.misc import _normalize_metadata_field_name
16
16
  from numpy import exp
17
17
  from qdrant_client.http import models as rest
@@ -1432,7 +1432,6 @@ class QdrantDocumentStore:
1432
1432
  :returns:
1433
1433
  The deserialized component.
1434
1434
  """
1435
- deserialize_secrets_inplace(data["init_parameters"], keys=["api_key"])
1436
1435
  return default_from_dict(cls, data)
1437
1436
 
1438
1437
  def to_dict(self) -> dict[str, Any]:
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  from collections.abc import Callable
2
6
  from datetime import datetime
3
7
  from typing import Any
@@ -131,8 +135,6 @@ def _build_comparison_condition(operator: str, key: str, value: Any) -> models.C
131
135
 
132
136
 
133
137
  def _build_eq_condition(key: str, value: models.ValueVariants) -> models.Condition:
134
- if isinstance(value, str) and " " in value:
135
- return models.FieldCondition(key=key, match=models.MatchText(text=value))
136
138
  return models.FieldCondition(key=key, match=models.MatchValue(value=value))
137
139
 
138
140
 
@@ -140,44 +142,18 @@ def _build_in_condition(key: str, value: list[models.ValueVariants]) -> models.C
140
142
  if not isinstance(value, list):
141
143
  msg = f"Value {value} is not a list"
142
144
  raise FilterError(msg)
143
- return models.Filter(
144
- should=[
145
- (
146
- models.FieldCondition(key=key, match=models.MatchText(text=item))
147
- if isinstance(item, str) and " " not in item
148
- else models.FieldCondition(key=key, match=models.MatchValue(value=item))
149
- )
150
- for item in value
151
- ]
152
- )
145
+ return models.Filter(should=[_build_eq_condition(key, item) for item in value])
153
146
 
154
147
 
155
148
  def _build_ne_condition(key: str, value: models.ValueVariants) -> models.Condition:
156
- return models.Filter(
157
- must_not=[
158
- (
159
- models.FieldCondition(key=key, match=models.MatchText(text=value))
160
- if isinstance(value, str) and " " not in value
161
- else models.FieldCondition(key=key, match=models.MatchValue(value=value))
162
- )
163
- ]
164
- )
149
+ return models.Filter(must_not=[_build_eq_condition(key, value)])
165
150
 
166
151
 
167
152
  def _build_nin_condition(key: str, value: list[models.ValueVariants]) -> models.Condition:
168
153
  if not isinstance(value, list):
169
154
  msg = f"Value {value} is not a list"
170
155
  raise FilterError(msg)
171
- return models.Filter(
172
- must_not=[
173
- (
174
- models.FieldCondition(key=key, match=models.MatchText(text=item))
175
- if isinstance(item, str) and " " in item
176
- else models.FieldCondition(key=key, match=models.MatchValue(value=item))
177
- )
178
- for item in value
179
- ]
180
- )
156
+ return models.Filter(must_not=[_build_eq_condition(key, item) for item in value])
181
157
 
182
158
 
183
159
  def _build_lt_condition(key: str, value: str | float | int) -> models.Condition:
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  import pytest
2
6
  from haystack import Document
3
7
  from haystack.testing.document_store import FilterDocumentsTest
@@ -49,16 +53,6 @@ class TestConvertFiltersToQdrantUnit:
49
53
  )
50
54
  assert isinstance(qdrant_filter, models.Filter)
51
55
 
52
- def test_eq_with_spaces_uses_text_match(self):
53
- qdrant_filter = convert_filters_to_qdrant({"operator": "==", "field": "meta.title", "value": "hello world"})
54
- condition = qdrant_filter.must[0]
55
- assert isinstance(condition.match, models.MatchText)
56
-
57
- def test_eq_without_spaces_uses_value_match(self):
58
- qdrant_filter = convert_filters_to_qdrant({"operator": "==", "field": "meta.name", "value": "name_0"})
59
- condition = qdrant_filter.must[0]
60
- assert isinstance(condition.match, models.MatchValue)
61
-
62
56
  def test_single_logical_condition_unwrapped(self):
63
57
  qdrant_filter = convert_filters_to_qdrant(
64
58
  {
@@ -96,6 +90,54 @@ class TestQdrantFilters(FilterDocumentsTest):
96
90
  wait_result_from_api=True,
97
91
  )
98
92
 
93
+ @pytest.mark.parametrize(("value", "longer_value"), [("news", "newsletter"), ("New York", "New York City")])
94
+ def test_eq_matches_exact_string(self, document_store, value, longer_value):
95
+ exact_document = Document(content=value, meta={"name": value})
96
+ other_document = Document(content=longer_value, meta={"name": longer_value})
97
+ document_store.write_documents([exact_document, other_document])
98
+
99
+ result = document_store.filter_documents(filters={"field": "meta.name", "operator": "==", "value": value})
100
+
101
+ self.assert_documents_are_equal(result, [exact_document])
102
+
103
+ @pytest.mark.parametrize(("value", "longer_value"), [("news", "newsletter"), ("New York", "New York City")])
104
+ def test_ne_excludes_exact_string(self, document_store, value, longer_value):
105
+ exact_document = Document(content=value, meta={"name": value})
106
+ other_document = Document(content=longer_value, meta={"name": longer_value})
107
+ document_store.write_documents([exact_document, other_document])
108
+
109
+ result = document_store.filter_documents(filters={"field": "meta.name", "operator": "!=", "value": value})
110
+
111
+ self.assert_documents_are_equal(result, [other_document])
112
+
113
+ def test_in_matches_exact_strings(self, document_store):
114
+ document_store.write_documents(
115
+ [
116
+ Document(content=name, meta={"name": name})
117
+ for name in ["news", "newsletter", "New York", "New York City"]
118
+ ]
119
+ )
120
+
121
+ result = document_store.filter_documents(
122
+ filters={"field": "meta.name", "operator": "in", "value": ["news", "New York"]}
123
+ )
124
+
125
+ assert sorted(doc.meta["name"] for doc in result) == ["New York", "news"]
126
+
127
+ def test_not_in_excludes_exact_strings(self, document_store):
128
+ document_store.write_documents(
129
+ [
130
+ Document(content=name, meta={"name": name})
131
+ for name in ["news", "newsletter", "New York", "New York City"]
132
+ ]
133
+ )
134
+
135
+ result = document_store.filter_documents(
136
+ filters={"field": "meta.name", "operator": "not in", "value": ["news", "New York"]}
137
+ )
138
+
139
+ assert sorted(doc.meta["name"] for doc in result) == ["New York City", "newsletter"]
140
+
99
141
  def test_filter_documents_with_qdrant_filters(self, document_store, filterable_docs):
100
142
  document_store.write_documents(filterable_docs)
101
143
  result = document_store.filter_documents(