qdrant-haystack 4.1.0__py3-none-any.whl → 4.1.2__py3-none-any.whl

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.

Potentially problematic release.


This version of qdrant-haystack might be problematic. Click here for more details.

@@ -2,6 +2,8 @@ from typing import Any, Dict, List, Optional, Union
2
2
 
3
3
  from haystack import Document, component, default_from_dict, default_to_dict
4
4
  from haystack.dataclasses.sparse_embedding import SparseEmbedding
5
+ from haystack.document_stores.types import FilterPolicy
6
+ from haystack.document_stores.types.filter_policy import apply_filter_policy
5
7
  from haystack_integrations.document_stores.qdrant import QdrantDocumentStore
6
8
  from qdrant_client.http import models
7
9
 
@@ -39,6 +41,7 @@ class QdrantEmbeddingRetriever:
39
41
  top_k: int = 10,
40
42
  scale_score: bool = False,
41
43
  return_embedding: bool = False,
44
+ filter_policy: Union[str, FilterPolicy] = FilterPolicy.REPLACE,
42
45
  score_threshold: Optional[float] = None,
43
46
  ):
44
47
  """
@@ -49,6 +52,7 @@ class QdrantEmbeddingRetriever:
49
52
  :param top_k: The maximum number of documents to retrieve.
50
53
  :param scale_score: Whether to scale the scores of the retrieved documents or not.
51
54
  :param return_embedding: Whether to return the embedding of the retrieved Documents.
55
+ :param filter_policy: Policy to determine how filters are applied.
52
56
  :param score_threshold: A minimal score threshold for the result.
53
57
  Score of the returned result might be higher or smaller than the threshold
54
58
  depending on the `similarity` function specified in the Document Store.
@@ -66,6 +70,9 @@ class QdrantEmbeddingRetriever:
66
70
  self._top_k = top_k
67
71
  self._scale_score = scale_score
68
72
  self._return_embedding = return_embedding
73
+ self._filter_policy = (
74
+ filter_policy if isinstance(filter_policy, FilterPolicy) else FilterPolicy.from_str(filter_policy)
75
+ )
69
76
  self._score_threshold = score_threshold
70
77
 
71
78
  def to_dict(self) -> Dict[str, Any]:
@@ -80,6 +87,7 @@ class QdrantEmbeddingRetriever:
80
87
  document_store=self._document_store,
81
88
  filters=self._filters,
82
89
  top_k=self._top_k,
90
+ filter_policy=self._filter_policy.value,
83
91
  scale_score=self._scale_score,
84
92
  return_embedding=self._return_embedding,
85
93
  score_threshold=self._score_threshold,
@@ -100,6 +108,10 @@ class QdrantEmbeddingRetriever:
100
108
  """
101
109
  document_store = QdrantDocumentStore.from_dict(data["init_parameters"]["document_store"])
102
110
  data["init_parameters"]["document_store"] = document_store
111
+ # Pipelines serialized with old versions of the component might not
112
+ # have the filter_policy field.
113
+ if filter_policy := data["init_parameters"].get("filter_policy"):
114
+ data["init_parameters"]["filter_policy"] = FilterPolicy.from_str(filter_policy)
103
115
  return default_from_dict(cls, data)
104
116
 
105
117
  @component.output_types(documents=List[Document])
@@ -125,9 +137,11 @@ class QdrantEmbeddingRetriever:
125
137
  The retrieved documents.
126
138
 
127
139
  """
140
+ filters = apply_filter_policy(self._filter_policy, self._filters, filters)
141
+
128
142
  docs = self._document_store._query_by_embedding(
129
143
  query_embedding=query_embedding,
130
- filters=filters or self._filters,
144
+ filters=filters,
131
145
  top_k=top_k or self._top_k,
132
146
  scale_score=scale_score or self._scale_score,
133
147
  return_embedding=return_embedding or self._return_embedding,
@@ -171,6 +185,7 @@ class QdrantSparseEmbeddingRetriever:
171
185
  top_k: int = 10,
172
186
  scale_score: bool = False,
173
187
  return_embedding: bool = False,
188
+ filter_policy: Union[str, FilterPolicy] = FilterPolicy.REPLACE,
174
189
  score_threshold: Optional[float] = None,
175
190
  ):
176
191
  """
@@ -181,6 +196,7 @@ class QdrantSparseEmbeddingRetriever:
181
196
  :param top_k: The maximum number of documents to retrieve.
182
197
  :param scale_score: Whether to scale the scores of the retrieved documents or not.
183
198
  :param return_embedding: Whether to return the sparse embedding of the retrieved Documents.
199
+ :param filter_policy: Policy to determine how filters are applied. Defaults to "replace".
184
200
  :param score_threshold: A minimal score threshold for the result.
185
201
  Score of the returned result might be higher or smaller than the threshold
186
202
  depending on the Distance function used.
@@ -198,6 +214,9 @@ class QdrantSparseEmbeddingRetriever:
198
214
  self._top_k = top_k
199
215
  self._scale_score = scale_score
200
216
  self._return_embedding = return_embedding
217
+ self._filter_policy = (
218
+ filter_policy if isinstance(filter_policy, FilterPolicy) else FilterPolicy.from_str(filter_policy)
219
+ )
201
220
  self._score_threshold = score_threshold
202
221
 
203
222
  def to_dict(self) -> Dict[str, Any]:
@@ -213,6 +232,7 @@ class QdrantSparseEmbeddingRetriever:
213
232
  filters=self._filters,
214
233
  top_k=self._top_k,
215
234
  scale_score=self._scale_score,
235
+ filter_policy=self._filter_policy.value,
216
236
  return_embedding=self._return_embedding,
217
237
  score_threshold=self._score_threshold,
218
238
  )
@@ -232,6 +252,10 @@ class QdrantSparseEmbeddingRetriever:
232
252
  """
233
253
  document_store = QdrantDocumentStore.from_dict(data["init_parameters"]["document_store"])
234
254
  data["init_parameters"]["document_store"] = document_store
255
+ # Pipelines serialized with old versions of the component might not
256
+ # have the filter_policy field.
257
+ if filter_policy := data["init_parameters"].get("filter_policy"):
258
+ data["init_parameters"]["filter_policy"] = FilterPolicy.from_str(filter_policy)
235
259
  return default_from_dict(cls, data)
236
260
 
237
261
  @component.output_types(documents=List[Document])
@@ -248,7 +272,9 @@ class QdrantSparseEmbeddingRetriever:
248
272
  Run the Sparse Embedding Retriever on the given input data.
249
273
 
250
274
  :param query_sparse_embedding: Sparse Embedding of the query.
251
- :param filters: A dictionary with filters to narrow down the search space.
275
+ :param filters: Filters applied to the retrieved Documents. The way runtime filters are applied depends on
276
+ the `filter_policy` chosen at retriever initialization. See init method docstring for more
277
+ details.
252
278
  :param top_k: The maximum number of documents to return.
253
279
  :param scale_score: Whether to scale the scores of the retrieved documents or not.
254
280
  :param return_embedding: Whether to return the embedding of the retrieved Documents.
@@ -260,9 +286,11 @@ class QdrantSparseEmbeddingRetriever:
260
286
  The retrieved documents.
261
287
 
262
288
  """
289
+ filters = apply_filter_policy(self._filter_policy, self._filters, filters)
290
+
263
291
  docs = self._document_store._query_by_sparse(
264
292
  query_sparse_embedding=query_sparse_embedding,
265
- filters=filters or self._filters,
293
+ filters=filters,
266
294
  top_k=top_k or self._top_k,
267
295
  scale_score=scale_score or self._scale_score,
268
296
  return_embedding=return_embedding or self._return_embedding,
@@ -311,6 +339,7 @@ class QdrantHybridRetriever:
311
339
  filters: Optional[Union[Dict[str, Any], models.Filter]] = None,
312
340
  top_k: int = 10,
313
341
  return_embedding: bool = False,
342
+ filter_policy: Union[str, FilterPolicy] = FilterPolicy.REPLACE,
314
343
  score_threshold: Optional[float] = None,
315
344
  ):
316
345
  """
@@ -320,6 +349,7 @@ class QdrantHybridRetriever:
320
349
  :param filters: A dictionary with filters to narrow down the search space.
321
350
  :param top_k: The maximum number of documents to retrieve.
322
351
  :param return_embedding: Whether to return the embeddings of the retrieved Documents.
352
+ :param filter_policy: Policy to determine how filters are applied.
323
353
  :param score_threshold: A minimal score threshold for the result.
324
354
  Score of the returned result might be higher or smaller than the threshold
325
355
  depending on the Distance function used.
@@ -336,6 +366,9 @@ class QdrantHybridRetriever:
336
366
  self._filters = filters
337
367
  self._top_k = top_k
338
368
  self._return_embedding = return_embedding
369
+ self._filter_policy = (
370
+ filter_policy if isinstance(filter_policy, FilterPolicy) else FilterPolicy.from_str(filter_policy)
371
+ )
339
372
  self._score_threshold = score_threshold
340
373
 
341
374
  def to_dict(self) -> Dict[str, Any]:
@@ -350,6 +383,7 @@ class QdrantHybridRetriever:
350
383
  document_store=self._document_store.to_dict(),
351
384
  filters=self._filters,
352
385
  top_k=self._top_k,
386
+ filter_policy=self._filter_policy.value,
353
387
  return_embedding=self._return_embedding,
354
388
  score_threshold=self._score_threshold,
355
389
  )
@@ -366,6 +400,10 @@ class QdrantHybridRetriever:
366
400
  """
367
401
  document_store = QdrantDocumentStore.from_dict(data["init_parameters"]["document_store"])
368
402
  data["init_parameters"]["document_store"] = document_store
403
+ # Pipelines serialized with old versions of the component might not
404
+ # have the filter_policy field.
405
+ if filter_policy := data["init_parameters"].get("filter_policy"):
406
+ data["init_parameters"]["filter_policy"] = FilterPolicy.from_str(filter_policy)
369
407
  return default_from_dict(cls, data)
370
408
 
371
409
  @component.output_types(documents=List[Document])
@@ -383,7 +421,9 @@ class QdrantHybridRetriever:
383
421
 
384
422
  :param query_embedding: Dense embedding of the query.
385
423
  :param query_sparse_embedding: Sparse embedding of the query.
386
- :param filters: A dictionary with filters to narrow down the search space.
424
+ :param filters: Filters applied to the retrieved Documents. The way runtime filters are applied depends on
425
+ the `filter_policy` chosen at retriever initialization. See init method docstring for more
426
+ details.
387
427
  :param top_k: The maximum number of documents to return.
388
428
  :param return_embedding: Whether to return the embedding of the retrieved Documents.
389
429
  :param score_threshold: A minimal score threshold for the result.
@@ -394,10 +434,12 @@ class QdrantHybridRetriever:
394
434
  The retrieved documents.
395
435
 
396
436
  """
437
+ filters = apply_filter_policy(self._filter_policy, self._filters, filters)
438
+
397
439
  docs = self._document_store._query_hybrid(
398
440
  query_embedding=query_embedding,
399
441
  query_sparse_embedding=query_sparse_embedding,
400
- filters=filters or self._filters,
442
+ filters=filters,
401
443
  top_k=top_k or self._top_k,
402
444
  return_embedding=return_embedding or self._return_embedding,
403
445
  score_threshold=score_threshold or self._score_threshold,
@@ -4,28 +4,55 @@ from typing import List, Optional, Union
4
4
  from haystack.utils.filters import COMPARISON_OPERATORS, LOGICAL_OPERATORS, FilterError
5
5
  from qdrant_client.http import models
6
6
 
7
- from .converters import convert_id
8
-
9
7
  COMPARISON_OPERATORS = COMPARISON_OPERATORS.keys()
10
8
  LOGICAL_OPERATORS = LOGICAL_OPERATORS.keys()
11
9
 
12
10
 
13
11
  def convert_filters_to_qdrant(
14
- filter_term: Optional[Union[List[dict], dict, models.Filter]] = None,
15
- ) -> Optional[models.Filter]:
16
- """Converts Haystack filters to the format used by Qdrant."""
12
+ filter_term: Optional[Union[List[dict], dict, models.Filter]] = None, is_parent_call: bool = True
13
+ ) -> Optional[Union[models.Filter, List[models.Filter], List[models.Condition]]]:
14
+ """Converts Haystack filters to the format used by Qdrant.
15
+
16
+ :param filter_term: the haystack filter to be converted to qdrant.
17
+ :param is_parent_call: indicates if this is the top-level call to the function. If True, the function returns
18
+ a single models.Filter object; if False, it may return a list of filters or conditions for further processing.
19
+
20
+ :returns: a single Qdrant Filter in the parent call or a list of such Filters in recursive calls.
21
+
22
+ :raises FilterError: If the invalid filter criteria is provided or if an unknown operator is encountered.
23
+
24
+ """
25
+
17
26
  if isinstance(filter_term, models.Filter):
18
27
  return filter_term
19
28
  if not filter_term:
20
29
  return None
21
30
 
22
- must_clauses, should_clauses, must_not_clauses = [], [], []
31
+ must_clauses: List[models.Filter] = []
32
+ should_clauses: List[models.Filter] = []
33
+ must_not_clauses: List[models.Filter] = []
34
+ # Indicates if there are multiple same LOGICAL OPERATORS on each level
35
+ # and prevents them from being combined
36
+ same_operator_flag = False
37
+ conditions, qdrant_filter, current_level_operators = (
38
+ [],
39
+ [],
40
+ [],
41
+ )
23
42
 
24
43
  if isinstance(filter_term, dict):
25
44
  filter_term = [filter_term]
26
45
 
46
+ # ======== IDENTIFY FILTER ITEMS ON EACH LEVEL ========
47
+
27
48
  for item in filter_term:
28
49
  operator = item.get("operator")
50
+
51
+ # Check for repeated similar operators on each level
52
+ same_operator_flag = operator in current_level_operators and operator in LOGICAL_OPERATORS
53
+ if not same_operator_flag:
54
+ current_level_operators.append(operator)
55
+
29
56
  if operator is None:
30
57
  msg = "Operator not found in filters"
31
58
  raise FilterError(msg)
@@ -34,12 +61,23 @@ def convert_filters_to_qdrant(
34
61
  msg = f"'conditions' not found for '{operator}'"
35
62
  raise FilterError(msg)
36
63
 
37
- if operator == "AND":
38
- must_clauses.append(convert_filters_to_qdrant(item.get("conditions", [])))
39
- elif operator == "OR":
40
- should_clauses.append(convert_filters_to_qdrant(item.get("conditions", [])))
41
- elif operator == "NOT":
42
- must_not_clauses.append(convert_filters_to_qdrant(item.get("conditions", [])))
64
+ if operator in LOGICAL_OPERATORS:
65
+ # Recursively process nested conditions
66
+ current_filter = convert_filters_to_qdrant(item.get("conditions", []), is_parent_call=False) or []
67
+
68
+ # When same_operator_flag is set to True,
69
+ # ensure each clause is appended as an independent list to avoid merging distinct clauses.
70
+ if operator == "AND":
71
+ must_clauses = [must_clauses, current_filter] if same_operator_flag else must_clauses + current_filter
72
+ elif operator == "OR":
73
+ should_clauses = (
74
+ [should_clauses, current_filter] if same_operator_flag else should_clauses + current_filter
75
+ )
76
+ elif operator == "NOT":
77
+ must_not_clauses = (
78
+ [must_not_clauses, current_filter] if same_operator_flag else must_not_clauses + current_filter
79
+ )
80
+
43
81
  elif operator in COMPARISON_OPERATORS:
44
82
  field = item.get("field")
45
83
  value = item.get("value")
@@ -47,20 +85,106 @@ def convert_filters_to_qdrant(
47
85
  msg = f"'field' or 'value' not found for '{operator}'"
48
86
  raise FilterError(msg)
49
87
 
50
- must_clauses.extend(_parse_comparison_operation(comparison_operation=operator, key=field, value=value))
88
+ parsed_conditions = _parse_comparison_operation(comparison_operation=operator, key=field, value=value)
89
+
90
+ # check if the parsed_conditions are models.Filter or models.Condition
91
+ for condition in parsed_conditions:
92
+ if isinstance(condition, models.Filter):
93
+ qdrant_filter.append(condition)
94
+ else:
95
+ conditions.append(condition)
96
+
51
97
  else:
52
98
  msg = f"Unknown operator {operator} used in filters"
53
99
  raise FilterError(msg)
54
100
 
55
- payload_filter = models.Filter(
56
- must=must_clauses or None,
57
- should=should_clauses or None,
58
- must_not=must_not_clauses or None,
59
- )
101
+ # ======== PROCESS FILTER ITEMS ON EACH LEVEL ========
102
+
103
+ # If same logical operators have separate clauses, create separate filters
104
+ if same_operator_flag:
105
+ qdrant_filter = build_filters_for_repeated_operators(
106
+ must_clauses, should_clauses, must_not_clauses, qdrant_filter
107
+ )
108
+
109
+ # else append a single Filter for existing clauses
110
+ elif must_clauses or should_clauses or must_not_clauses:
111
+ qdrant_filter.append(
112
+ models.Filter(
113
+ must=must_clauses or None,
114
+ should=should_clauses or None,
115
+ must_not=must_not_clauses or None,
116
+ )
117
+ )
118
+
119
+ # In case of parent call, a single Filter is returned
120
+ if is_parent_call:
121
+ # If qdrant_filter has just a single Filter in parent call,
122
+ # then it might be returned instead.
123
+ if len(qdrant_filter) == 1 and isinstance(qdrant_filter[0], models.Filter):
124
+ return qdrant_filter[0]
125
+ else:
126
+ must_clauses.extend(conditions)
127
+ return models.Filter(
128
+ must=must_clauses or None,
129
+ should=should_clauses or None,
130
+ must_not=must_not_clauses or None,
131
+ )
132
+
133
+ # Store conditions of each level in output of the loop
134
+ elif conditions:
135
+ qdrant_filter.extend(conditions)
136
+
137
+ return qdrant_filter
138
+
139
+
140
+ def build_filters_for_repeated_operators(
141
+ must_clauses,
142
+ should_clauses,
143
+ must_not_clauses,
144
+ qdrant_filter,
145
+ ) -> List[models.Filter]:
146
+ """
147
+ Flattens the nested lists of clauses by creating separate Filters for each clause of a logical operator.
148
+
149
+ :param must_clauses: a nested list of must clauses or an empty list.
150
+ :param should_clauses: a nested list of should clauses or an empty list.
151
+ :param must_not_clauses: a nested list of must_not clauses or an empty list.
152
+ :param qdrant_filter: a list where the generated Filter objects will be appended.
153
+ This list will be modified in-place.
60
154
 
61
- filter_result = _squeeze_filter(payload_filter)
62
155
 
63
- return filter_result
156
+ :returns: the modified `qdrant_filter` list with appended generated Filter objects.
157
+ """
158
+
159
+ if any(isinstance(i, list) for i in must_clauses):
160
+ for i in must_clauses:
161
+ qdrant_filter.append(
162
+ models.Filter(
163
+ must=i or None,
164
+ should=should_clauses or None,
165
+ must_not=must_not_clauses or None,
166
+ )
167
+ )
168
+ if any(isinstance(i, list) for i in should_clauses):
169
+ for i in should_clauses:
170
+ qdrant_filter.append(
171
+ models.Filter(
172
+ must=must_clauses or None,
173
+ should=i or None,
174
+ must_not=must_not_clauses or None,
175
+ )
176
+ )
177
+ if any(isinstance(i, list) for i in must_not_clauses):
178
+ for i in must_clauses:
179
+ qdrant_filter.append(
180
+ models.Filter(
181
+ must=must_clauses or None,
182
+ should=should_clauses or None,
183
+ must_not=i or None,
184
+ )
185
+ )
186
+
187
+ return qdrant_filter
64
188
 
65
189
 
66
190
  def _parse_comparison_operation(
@@ -92,7 +216,7 @@ def _parse_comparison_operation(
92
216
 
93
217
  def _build_eq_condition(key: str, value: models.ValueVariants) -> models.Condition:
94
218
  if isinstance(value, str) and " " in value:
95
- models.FieldCondition(key=key, match=models.MatchText(text=value))
219
+ return models.FieldCondition(key=key, match=models.MatchText(text=value))
96
220
  return models.FieldCondition(key=key, match=models.MatchValue(value=value))
97
221
 
98
222
 
@@ -184,52 +308,6 @@ def _build_gte_condition(key: str, value: Union[str, float, int]) -> models.Cond
184
308
  raise FilterError(msg)
185
309
 
186
310
 
187
- def _build_has_id_condition(id_values: List[models.ExtendedPointId]) -> models.HasIdCondition:
188
- return models.HasIdCondition(
189
- has_id=[
190
- # Ids are converted into their internal representation
191
- convert_id(item)
192
- for item in id_values
193
- ]
194
- )
195
-
196
-
197
- def _squeeze_filter(payload_filter: models.Filter) -> models.Filter:
198
- """
199
- Simplify given payload filter, if the nested structure might be unnested.
200
- That happens if there is a single clause in that filter.
201
- :param payload_filter:
202
- :returns:
203
- """
204
- filter_parts = {
205
- "must": payload_filter.must,
206
- "should": payload_filter.should,
207
- "must_not": payload_filter.must_not,
208
- }
209
-
210
- total_clauses = sum(len(x) for x in filter_parts.values() if x is not None)
211
- if total_clauses == 0 or total_clauses > 1:
212
- return payload_filter
213
-
214
- # Payload filter has just a single clause provided (either must, should
215
- # or must_not). If that single clause is also of a models.Filter type,
216
- # then it might be returned instead.
217
- for part_name, filter_part in filter_parts.items():
218
- if not filter_part:
219
- continue
220
-
221
- subfilter = filter_part[0]
222
- if not isinstance(subfilter, models.Filter):
223
- # The inner statement is a simple condition like models.FieldCondition
224
- # so it cannot be simplified.
225
- continue
226
-
227
- if subfilter.must:
228
- return models.Filter(**{part_name: subfilter.must})
229
-
230
- return payload_filter
231
-
232
-
233
311
  def is_datetime_string(value: str) -> bool:
234
312
  try:
235
313
  datetime.fromisoformat(value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qdrant-haystack
3
- Version: 4.1.0
3
+ Version: 4.1.2
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
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: Implementation :: CPython
19
19
  Classifier: Programming Language :: Python :: Implementation :: PyPy
20
20
  Requires-Python: >=3.8
21
- Requires-Dist: haystack-ai>=2.0.1
21
+ Requires-Dist: haystack-ai
22
22
  Requires-Dist: qdrant-client>=1.10.0
23
23
  Description-Content-Type: text/markdown
24
24
 
@@ -1,11 +1,11 @@
1
1
  haystack_integrations/components/retrievers/qdrant/__init__.py,sha256=IRjcM4f8b5eKFEMn8tn6h6RrfslEGP3WafU7mrzNzQM,313
2
- haystack_integrations/components/retrievers/qdrant/retriever.py,sha256=acSMsrtVbkU1xpihH6oeIaKy6SGnQvzERZ3TWJvUViY,15826
2
+ haystack_integrations/components/retrievers/qdrant/retriever.py,sha256=qM3zopQmbPo_mejnFGZ24TMr1HR_jL7tc90M5fnYPOw,18448
3
3
  haystack_integrations/document_stores/qdrant/__init__.py,sha256=kUGc5uewqArhmVR-JqB_NmJ4kNkTIQIvYDNSoO2ELn0,302
4
4
  haystack_integrations/document_stores/qdrant/converters.py,sha256=2hcuI3kty1dVHzX1WGXxEtlrnZ9E8TAG56XATCFa6Pw,2491
5
5
  haystack_integrations/document_stores/qdrant/document_store.py,sha256=eLw4P1h8GCj40R-BIlQOvJG9MpDzvtmQ7Hpb3AZhMSo,38117
6
- haystack_integrations/document_stores/qdrant/filters.py,sha256=0w70Wa3Za1fNdbJ5O95sZDIpXfblJG_sBBUv0JTQ0-o,8337
6
+ haystack_integrations/document_stores/qdrant/filters.py,sha256=Nv_eKIYKwUWvldJfa0omfFQ0kgqi6L3DUFeMuIWziOY,11751
7
7
  haystack_integrations/document_stores/qdrant/migrate_to_sparse.py,sha256=i6wBC_9_JVzYZtqKm3dhHKTxhwNdcAdpgki8GABDp1c,4909
8
- qdrant_haystack-4.1.0.dist-info/METADATA,sha256=wGNRbZYtVJ3BbHH22ZsoVhGGtyCR3cNeozFKHiAsYjI,1870
9
- qdrant_haystack-4.1.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
10
- qdrant_haystack-4.1.0.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
11
- qdrant_haystack-4.1.0.dist-info/RECORD,,
8
+ qdrant_haystack-4.1.2.dist-info/METADATA,sha256=Yr9ah4TZp8JjwYndH0kbQt4moQe52EhR3WWKoSni89k,1863
9
+ qdrant_haystack-4.1.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
10
+ qdrant_haystack-4.1.2.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
11
+ qdrant_haystack-4.1.2.dist-info/RECORD,,