nucliadb 6.4.0.post4204__py3-none-any.whl → 6.4.0.post4210__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.
@@ -55,16 +55,19 @@ class VectorsetExternalIndex:
55
55
  similarity: VectorSimilarity.ValueType
56
56
 
57
57
 
58
- class TextBlockMatch(BaseModel):
58
+ class ScoredTextBlock(BaseModel):
59
+ paragraph_id: ParagraphId
60
+ score: float
61
+ score_type: SCORE_TYPE
62
+
63
+
64
+ class TextBlockMatch(ScoredTextBlock):
59
65
  """
60
66
  Model a text block/paragraph retrieved from an external index with all the information
61
67
  needed in order to later hydrate retrieval results.
62
68
  """
63
69
 
64
- paragraph_id: ParagraphId
65
70
  position: TextPosition
66
- score: float
67
- score_type: SCORE_TYPE
68
71
  order: int
69
72
  page_with_visual: bool = False
70
73
  fuzzy_search: bool
@@ -33,6 +33,8 @@ from nuclia_models.predict.generative_responses import (
33
33
  from pydantic_core import ValidationError
34
34
 
35
35
  from nucliadb.common.datamanagers.exceptions import KnowledgeBoxNotFound
36
+ from nucliadb.common.external_index_providers.base import ScoredTextBlock
37
+ from nucliadb.common.ids import ParagraphId
36
38
  from nucliadb.models.responses import HTTPClientError
37
39
  from nucliadb.search import logger, predict
38
40
  from nucliadb.search.predict import (
@@ -63,6 +65,7 @@ from nucliadb.search.search.graph_strategy import get_graph_results
63
65
  from nucliadb.search.search.metrics import RAGMetrics
64
66
  from nucliadb.search.search.query_parser.fetcher import Fetcher
65
67
  from nucliadb.search.search.query_parser.parsers.ask import fetcher_for_ask, parse_ask
68
+ from nucliadb.search.search.rank_fusion import WeightedCombSum
66
69
  from nucliadb.search.search.rerankers import (
67
70
  get_reranker,
68
71
  )
@@ -865,6 +868,10 @@ async def retrieval_in_resource(
865
868
  )
866
869
 
867
870
 
871
+ class _FindParagraph(ScoredTextBlock):
872
+ original: FindParagraph
873
+
874
+
868
875
  def compute_best_matches(
869
876
  main_results: KnowledgeboxFindResults,
870
877
  prequeries_results: Optional[list[PreQueryResult]] = None,
@@ -882,42 +889,46 @@ def compute_best_matches(
882
889
  `main_query_weight` is the weight given to the paragraphs matching the main query when calculating the final score.
883
890
  """
884
891
 
885
- def iter_paragraphs(results: KnowledgeboxFindResults):
892
+ def extract_paragraphs(results: KnowledgeboxFindResults) -> list[_FindParagraph]:
893
+ paragraphs = []
886
894
  for resource in results.resources.values():
887
895
  for field in resource.fields.values():
888
896
  for paragraph in field.paragraphs.values():
889
- yield paragraph
890
-
891
- total_weights = main_query_weight + sum(prequery.weight for prequery, _ in prequeries_results or [])
892
- paragraph_id_to_match: dict[str, RetrievalMatch] = {}
893
- for paragraph in iter_paragraphs(main_results):
894
- normalized_weight = main_query_weight / total_weights
895
- rmatch = RetrievalMatch(
896
- paragraph=paragraph,
897
- weighted_score=paragraph.score * normalized_weight,
898
- )
899
- paragraph_id_to_match[paragraph.id] = rmatch
900
-
901
- for prequery, prequery_results in prequeries_results or []:
902
- for paragraph in iter_paragraphs(prequery_results):
903
- normalized_weight = prequery.weight / total_weights
904
- weighted_score = paragraph.score * normalized_weight
905
- if paragraph.id in paragraph_id_to_match:
906
- rmatch = paragraph_id_to_match[paragraph.id]
907
- # If a paragraph is matched in various prequeries, the final score is the
908
- # sum of the weighted scores
909
- rmatch.weighted_score += weighted_score
910
- else:
911
- paragraph_id_to_match[paragraph.id] = RetrievalMatch(
912
- paragraph=paragraph,
913
- weighted_score=weighted_score,
914
- )
897
+ paragraphs.append(
898
+ _FindParagraph(
899
+ paragraph_id=ParagraphId.from_string(paragraph.id),
900
+ score=paragraph.score,
901
+ score_type=paragraph.score_type,
902
+ original=paragraph,
903
+ )
904
+ )
905
+ return paragraphs
915
906
 
916
- return sorted(
917
- paragraph_id_to_match.values(),
918
- key=lambda match: match.weighted_score,
919
- reverse=True,
920
- )
907
+ weights = {
908
+ "main": main_query_weight,
909
+ }
910
+ total_weight = main_query_weight
911
+ find_results = {
912
+ "main": extract_paragraphs(main_results),
913
+ }
914
+ for i, (prequery, prequery_results) in enumerate(prequeries_results or []):
915
+ weights[f"prequery-{i}"] = prequery.weight
916
+ total_weight += prequery.weight
917
+ find_results[f"prequery-{i}"] = extract_paragraphs(prequery_results)
918
+
919
+ normalized_weights = {key: value / total_weight for key, value in weights.items()}
920
+
921
+ # window does nothing here
922
+ rank_fusion = WeightedCombSum(window=0, weights=normalized_weights)
923
+
924
+ merged = []
925
+ for item in rank_fusion.fuse(find_results):
926
+ match = RetrievalMatch(
927
+ paragraph=item.original,
928
+ weighted_score=item.score,
929
+ )
930
+ merged.append(match)
931
+ return merged
921
932
 
922
933
 
923
934
  def calculate_prequeries_for_json_schema(
@@ -42,7 +42,7 @@ from nucliadb.search.search.hydrator import (
42
42
  )
43
43
  from nucliadb.search.search.merge import merge_relations_results
44
44
  from nucliadb.search.search.query_parser.models import UnitRetrieval
45
- from nucliadb.search.search.rank_fusion import RankFusionAlgorithm
45
+ from nucliadb.search.search.rank_fusion import IndexSource, RankFusionAlgorithm
46
46
  from nucliadb.search.search.rerankers import (
47
47
  RerankableItem,
48
48
  Reranker,
@@ -108,7 +108,13 @@ async def build_find_response(
108
108
  )
109
109
  graph_results = graph_results_to_text_block_matches(search_response.graph)
110
110
 
111
- merged_text_blocks = rank_fusion_algorithm.fuse(keyword_results, semantic_results, graph_results)
111
+ merged_text_blocks = rank_fusion_algorithm.fuse(
112
+ {
113
+ IndexSource.KEYWORD: keyword_results,
114
+ IndexSource.SEMANTIC: semantic_results,
115
+ IndexSource.GRAPH: graph_results,
116
+ }
117
+ )
112
118
 
113
119
  # cut
114
120
  # we assume pagination + predict reranker is forbidden and has been already
@@ -19,8 +19,10 @@
19
19
  #
20
20
  import logging
21
21
  from abc import ABC, abstractmethod
22
+ from enum import Enum, auto
23
+ from typing import Optional, TypeVar
22
24
 
23
- from nucliadb.common.external_index_providers.base import TextBlockMatch
25
+ from nucliadb.common.external_index_providers.base import ScoredTextBlock
24
26
  from nucliadb.common.ids import ParagraphId
25
27
  from nucliadb.search.search.query_parser import models as parser_models
26
28
  from nucliadb_models.search import SCORE_TYPE
@@ -45,6 +47,14 @@ rank_fusion_observer = Observer(
45
47
  ],
46
48
  )
47
49
 
50
+ ScoredItem = TypeVar("ScoredItem", bound=ScoredTextBlock)
51
+
52
+
53
+ class IndexSource(str, Enum):
54
+ KEYWORD = auto()
55
+ SEMANTIC = auto()
56
+ GRAPH = auto()
57
+
48
58
 
49
59
  class RankFusionAlgorithm(ABC):
50
60
  def __init__(self, window: int):
@@ -60,46 +70,44 @@ class RankFusionAlgorithm(ABC):
60
70
  """
61
71
  return self._window
62
72
 
63
- def fuse(
64
- self,
65
- keyword: list[TextBlockMatch],
66
- semantic: list[TextBlockMatch],
67
- graph: list[TextBlockMatch],
68
- ) -> list[TextBlockMatch]:
69
- """Fuse keyword and semantic results and return a list with the merged
73
+ def fuse(self, sources: dict[str, list[ScoredItem]]) -> list[ScoredItem]:
74
+ """Fuse elements from multiple sources and return a list of merged
70
75
  results.
71
76
 
72
- If only one retriever is provided, rank fusion will be skipped.
77
+ If only one source is provided, rank fusion will be skipped.
73
78
 
74
79
  """
75
- # sort results by it's score before merging them
76
- keyword = [k for k in sorted(keyword, key=lambda r: r.score, reverse=True)]
77
- semantic = [s for s in sorted(semantic, key=lambda r: r.score, reverse=True)]
78
- graph = [g for g in graph]
79
-
80
- retrievals_with_results = [x for x in (keyword, semantic, graph) if len(x) > 0]
81
- if len(retrievals_with_results) == 1:
82
- return retrievals_with_results[0]
80
+ sources_with_results = [x for x in sources.values() if len(x) > 0]
81
+ if len(sources_with_results) == 1:
82
+ # skip rank fusion, we only have a source
83
+ merged = sources_with_results[0]
83
84
  else:
84
- merged = self._fuse(keyword, semantic, graph)
85
+ merged = self._fuse(sources)
86
+
87
+ # sort and return the unordered results from the implementation
88
+ merged.sort(key=lambda r: r.score, reverse=True)
85
89
  return merged
86
90
 
87
91
  @abstractmethod
88
- def _fuse(
89
- self,
90
- keyword: list[TextBlockMatch],
91
- semantic: list[TextBlockMatch],
92
- graph: list[TextBlockMatch],
93
- ) -> list[TextBlockMatch]:
94
- """Rank fusion implementation. All arguments are assumed to be ordered
95
- by decreasing score."""
92
+ def _fuse(self, sources: dict[str, list[ScoredItem]]) -> list[ScoredItem]:
93
+ """Rank fusion implementation.
94
+
95
+ Each concrete subclass must provide an implementation that merges
96
+ `sources`, a group of unordered matches, into a list of unordered
97
+ results with the new rank fusion score.
98
+
99
+ Results can be deduplicated or changed by the rank fusion algorithm.
100
+
101
+ """
96
102
  ...
97
103
 
98
104
 
99
105
  class ReciprocalRankFusion(RankFusionAlgorithm):
100
106
  """Rank-based rank fusion algorithm. Discounts the weight of documents
101
- occurring deep in retrieved lists using a reciprocal distribution. It can be
102
- parametrized with weights to boost retrievers.
107
+ occurring deep in retrieved lists using a reciprocal distribution.
108
+
109
+ This implementation can be further parametrized with a weight (boost) per
110
+ retriever that will be applied to all documents ranked by it.
103
111
 
104
112
  RRF = Σ(r ∈ R) (1 / (k + r(d)) · w(r))
105
113
 
@@ -119,9 +127,8 @@ class ReciprocalRankFusion(RankFusionAlgorithm):
119
127
  k: float = 60.0,
120
128
  *,
121
129
  window: int,
122
- keyword_weight: float = 1.0,
123
- semantic_weight: float = 1.0,
124
- graph_weight: float = 1.0,
130
+ weights: Optional[dict[str, float]] = None,
131
+ default_weight: float = 1.0,
125
132
  ):
126
133
  super().__init__(window)
127
134
  # Constant used in RRF, studies agree on 60 as a good default value
@@ -129,49 +136,118 @@ class ReciprocalRankFusion(RankFusionAlgorithm):
129
136
  # difference among the best results and a smaller score difference among
130
137
  # bad results
131
138
  self._k = k
132
- self._keyword_boost = keyword_weight
133
- self._semantic_boost = semantic_weight
134
- self._graph_boost = graph_weight
139
+ self._weights = weights or {}
140
+ self._default_weight = default_weight
135
141
 
136
142
  @rank_fusion_observer.wrap({"type": "reciprocal_rank_fusion"})
137
143
  def _fuse(
138
144
  self,
139
- keyword: list[TextBlockMatch],
140
- semantic: list[TextBlockMatch],
141
- graph: list[TextBlockMatch],
142
- ) -> list[TextBlockMatch]:
145
+ sources: dict[str, list[ScoredItem]],
146
+ ) -> list[ScoredItem]:
147
+ # accumulated scores per paragraph
148
+ scores: dict[ParagraphId, tuple[float, SCORE_TYPE]] = {}
149
+ # pointers from paragraph to the original source
150
+ match_positions: dict[ParagraphId, list[tuple[int, int]]] = {}
151
+
152
+ # sort results by it's score before fusing them, as we need the rank
153
+ sources = {
154
+ retriever: sorted(values, key=lambda r: r.score, reverse=True)
155
+ for retriever, values in sources.items()
156
+ }
157
+ rankings = [
158
+ (values, self._weights.get(source, self._default_weight))
159
+ for source, values in sources.items()
160
+ ]
161
+ for i, (ranking, weight) in enumerate(rankings):
162
+ for rank, item in enumerate(ranking):
163
+ id = item.paragraph_id
164
+ score, score_type = scores.setdefault(id, (0, item.score_type))
165
+ score += 1 / (self._k + rank) * weight
166
+ if {score_type, item.score_type} == {SCORE_TYPE.BM25, SCORE_TYPE.VECTOR}:
167
+ score_type = SCORE_TYPE.BOTH
168
+ scores[id] = (score, score_type)
169
+
170
+ position = (i, rank)
171
+ match_positions.setdefault(item.paragraph_id, []).append(position)
172
+
173
+ merged = []
174
+ for paragraph_id, positions in match_positions.items():
175
+ # we are getting only one position, effectively deduplicating
176
+ # multiple matches for the same text block
177
+ i, j = match_positions[paragraph_id][0]
178
+ score, score_type = scores[paragraph_id]
179
+ item = rankings[i][0][j]
180
+ item.score = score
181
+ item.score_type = score_type
182
+ merged.append(item)
183
+
184
+ return merged
185
+
186
+
187
+ class WeightedCombSum(RankFusionAlgorithm):
188
+ """Score-based rank fusion algorithm. Multiply each score by a list-specific
189
+ weight (boost). Then adds the retrieval score of documents contained in more
190
+ than one list and sort by score.
191
+
192
+ wCombSUM = Σ(r ∈ R) (w(r) · S(r, d))
193
+
194
+ where:
195
+ - d is a document
196
+ - R is the set of retrievers
197
+ - w(r) weight (boost) for retriever r
198
+ - S(r, d) is the score of document d given by retriever r
199
+
200
+ wCombSUM boosts matches from multiple retrievers and deduplicate them. As a
201
+ score ranking algorithm, comparison of different scores may lead to bad
202
+ results.
203
+
204
+ """
205
+
206
+ def __init__(
207
+ self,
208
+ *,
209
+ window: int,
210
+ weights: Optional[dict[str, float]] = None,
211
+ default_weight: float = 1.0,
212
+ ):
213
+ super().__init__(window)
214
+ self._weights = weights or {}
215
+ self._default_weight = default_weight
216
+
217
+ @rank_fusion_observer.wrap({"type": "weighted_comb_sum"})
218
+ def _fuse(self, sources: dict[str, list[ScoredItem]]) -> list[ScoredItem]:
219
+ # accumulated scores per paragraph
143
220
  scores: dict[ParagraphId, tuple[float, SCORE_TYPE]] = {}
221
+ # pointers from paragraph to the original source
144
222
  match_positions: dict[ParagraphId, list[tuple[int, int]]] = {}
145
223
 
146
224
  rankings = [
147
- (keyword, self._keyword_boost),
148
- (semantic, self._semantic_boost),
149
- (graph, self._graph_boost),
225
+ (values, self._weights.get(source, self._default_weight))
226
+ for source, values in sources.items()
150
227
  ]
151
- for r, (ranking, boost) in enumerate(rankings):
152
- for i, result in enumerate(ranking):
153
- id = result.paragraph_id
154
- score, score_type = scores.setdefault(id, (0, result.score_type))
155
- score += 1 / (self._k + i) * boost
156
- if {score_type, result.score_type} == {SCORE_TYPE.BM25, SCORE_TYPE.VECTOR}:
228
+ for i, (ranking, weight) in enumerate(rankings):
229
+ for j, item in enumerate(ranking):
230
+ id = item.paragraph_id
231
+ score, score_type = scores.setdefault(id, (0, item.score_type))
232
+ score += item.score * weight
233
+ if {score_type, item.score_type} == {SCORE_TYPE.BM25, SCORE_TYPE.VECTOR}:
157
234
  score_type = SCORE_TYPE.BOTH
158
235
  scores[id] = (score, score_type)
159
236
 
160
- position = (r, i)
161
- match_positions.setdefault(result.paragraph_id, []).append(position)
237
+ position = (i, j)
238
+ match_positions.setdefault(item.paragraph_id, []).append(position)
162
239
 
163
240
  merged = []
164
241
  for paragraph_id, positions in match_positions.items():
165
242
  # we are getting only one position, effectively deduplicating
166
243
  # multiple matches for the same text block
167
- r, i = match_positions[paragraph_id][0]
244
+ i, j = match_positions[paragraph_id][0]
168
245
  score, score_type = scores[paragraph_id]
169
- result = rankings[r][0][i]
170
- result.score = score
171
- result.score_type = score_type
172
- merged.append(result)
246
+ item = rankings[i][0][j]
247
+ item.score = score
248
+ item.score_type = score_type
249
+ merged.append(item)
173
250
 
174
- merged.sort(key=lambda x: x.score, reverse=True)
175
251
  return merged
176
252
 
177
253
 
@@ -184,9 +260,11 @@ def get_rank_fusion(rank_fusion: parser_models.RankFusion) -> RankFusionAlgorith
184
260
  algorithm = ReciprocalRankFusion(
185
261
  k=rank_fusion.k,
186
262
  window=window,
187
- keyword_weight=rank_fusion.boosting.keyword,
188
- semantic_weight=rank_fusion.boosting.semantic,
189
- graph_weight=rank_fusion.boosting.graph,
263
+ weights={
264
+ IndexSource.KEYWORD: rank_fusion.boosting.keyword,
265
+ IndexSource.SEMANTIC: rank_fusion.boosting.semantic,
266
+ IndexSource.GRAPH: rank_fusion.boosting.graph,
267
+ },
190
268
  )
191
269
 
192
270
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nucliadb
3
- Version: 6.4.0.post4204
3
+ Version: 6.4.0.post4210
4
4
  Summary: NucliaDB
5
5
  Author-email: Nuclia <nucliadb@nuclia.com>
6
6
  License: AGPL
@@ -20,11 +20,11 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3 :: Only
21
21
  Requires-Python: <4,>=3.9
22
22
  Description-Content-Type: text/markdown
23
- Requires-Dist: nucliadb-telemetry[all]>=6.4.0.post4204
24
- Requires-Dist: nucliadb-utils[cache,fastapi,storages]>=6.4.0.post4204
25
- Requires-Dist: nucliadb-protos>=6.4.0.post4204
26
- Requires-Dist: nucliadb-models>=6.4.0.post4204
27
- Requires-Dist: nidx-protos>=6.4.0.post4204
23
+ Requires-Dist: nucliadb-telemetry[all]>=6.4.0.post4210
24
+ Requires-Dist: nucliadb-utils[cache,fastapi,storages]>=6.4.0.post4210
25
+ Requires-Dist: nucliadb-protos>=6.4.0.post4210
26
+ Requires-Dist: nucliadb-models>=6.4.0.post4210
27
+ Requires-Dist: nidx-protos>=6.4.0.post4210
28
28
  Requires-Dist: nucliadb-admin-assets>=1.0.0.post1224
29
29
  Requires-Dist: nuclia-models>=0.24.2
30
30
  Requires-Dist: uvicorn[standard]
@@ -93,7 +93,7 @@ nucliadb/common/datamanagers/synonyms.py,sha256=zk3GEH38KF5vV_VcuL6DCg-2JwgXJfQl
93
93
  nucliadb/common/datamanagers/utils.py,sha256=McHlXvE4P3x-bBY3pr0n8djbTDQvI1G5WusJrnRdhLA,1827
94
94
  nucliadb/common/datamanagers/vectorsets.py,sha256=ciYb5uD435Zo8ZbqgPUAszFW9Svp_-R2hY2FEhQ411Y,4304
95
95
  nucliadb/common/external_index_providers/__init__.py,sha256=cp15ZcFnHvpcu_5-aK2A4uUyvuZVV_MJn4bIXMa20ks,835
96
- nucliadb/common/external_index_providers/base.py,sha256=K5lJc2IMcbMTm5rFeB96-mPgicBtGmaunF6GeE2qBxM,8863
96
+ nucliadb/common/external_index_providers/base.py,sha256=BL3DuYbnp-KCmGUiN-FGRtgjWj3SmtgMsGdjGq_7cX4,8905
97
97
  nucliadb/common/external_index_providers/exceptions.py,sha256=nDhhOIkb66hjCrBk4Spvl2vN1SuW5gbwrMCDmrdjHHE,1209
98
98
  nucliadb/common/external_index_providers/manager.py,sha256=aFSrrKKYG1ydpTSyq4zYD0LOxFS7P-CO6rcKC0hiF4I,4267
99
99
  nucliadb/common/external_index_providers/pinecone.py,sha256=sCvMgmXrLebnFLkpLnimDGrm4lE9ydb_ywotOMUSMrI,38124
@@ -239,7 +239,7 @@ nucliadb/search/search/exceptions.py,sha256=klGLgAGGrXcSGix_W6418ZBMqDchAIGjN77o
239
239
  nucliadb/search/search/fetch.py,sha256=eiljOKim-4OOEZn-3fyVZSYxztCH156BXYdqlIwVdN4,6181
240
240
  nucliadb/search/search/filters.py,sha256=1MkHlJjAQqoRCj7e5cEzK2HvBxGLE17I_omsjiklbtw,6476
241
241
  nucliadb/search/search/find.py,sha256=WTc_0HdbaU0Mwkmlf9s4pCmTuU0hz1jetBjIpNLXEEM,7982
242
- nucliadb/search/search/find_merge.py,sha256=2NtVZlR61MnTZl7mkzqcpIOPUU8hGWPrrP7ATeubt7w,19677
242
+ nucliadb/search/search/find_merge.py,sha256=c-7IlfjfdmWAvQOyM7IO3bKS1EQpnR4oi6pN6mwrQKw,19815
243
243
  nucliadb/search/search/graph_merge.py,sha256=y5V7X-BhjHsKDXE69tzQLIIKGm4XuaFrZXw0odcHVNM,3402
244
244
  nucliadb/search/search/graph_strategy.py,sha256=JsV-i9PGhekCAzmGpqeueQIitJb7fWCihIwUf76Q3pU,32912
245
245
  nucliadb/search/search/hydrator.py,sha256=-R37gCrGxkyaiHQalnTWHNG_FCx11Zucd7qA1vQCxuw,6985
@@ -250,13 +250,13 @@ nucliadb/search/search/paragraphs.py,sha256=pNAEiYqJGGUVcEf7xf-PFMVqz0PX4Qb-WNG-
250
250
  nucliadb/search/search/pgcatalog.py,sha256=s_J98fsX_RuFXwpejpkGqG-tD9ELuzz4YQ6U3ew5h2g,9313
251
251
  nucliadb/search/search/predict_proxy.py,sha256=IFI3v_ODz2_UU1XZnyaD391fE7-2C0npSmj_HmDvzS4,3123
252
252
  nucliadb/search/search/query.py,sha256=-gvKsyGmKYpsoEVzKkq3HJUMcs_3LD3TYUueOcJsTec,11511
253
- nucliadb/search/search/rank_fusion.py,sha256=8Bq3AHfHgytp7UIhJQgbnXrchrjlCQbgf8PhLQkWBPY,6718
253
+ nucliadb/search/search/rank_fusion.py,sha256=xZtXhbmKb_56gs73u6KkFm2efvTATOSMmpOV2wrAIqE,9613
254
254
  nucliadb/search/search/rerankers.py,sha256=PvhExUb8zZYghiFHRgGotw6h6bU--Rft09wE8arvtAw,7424
255
255
  nucliadb/search/search/shards.py,sha256=mc5DK-MoCv9AFhlXlOFHbPvetcyNDzTFOJ5rimK8PC8,2636
256
256
  nucliadb/search/search/summarize.py,sha256=ksmYPubEQvAQgfPdZHfzB_rR19B2ci4IYZ6jLdHxZo8,4996
257
257
  nucliadb/search/search/utils.py,sha256=ajRIXfdTF67dBVahQCXW-rSv6gJpUMPt3QhJrWqArTQ,2175
258
258
  nucliadb/search/search/chat/__init__.py,sha256=cp15ZcFnHvpcu_5-aK2A4uUyvuZVV_MJn4bIXMa20ks,835
259
- nucliadb/search/search/chat/ask.py,sha256=KO1YD1nGwblT4h2CfV7ccCpLP0OuoBCJQHBB5bDQatg,37476
259
+ nucliadb/search/search/chat/ask.py,sha256=jYOGh2rySV4aFx_D2KlNVbPXHBsbkcy0Ve-eBS7CSYc,37611
260
260
  nucliadb/search/search/chat/exceptions.py,sha256=Siy4GXW2L7oPhIR86H3WHBhE9lkV4A4YaAszuGGUf54,1356
261
261
  nucliadb/search/search/chat/images.py,sha256=PA8VWxT5_HUGfW1ULhKTK46UBsVyINtWWqEM1ulzX1E,3095
262
262
  nucliadb/search/search/chat/prompt.py,sha256=Jnja-Ss7skgnnDY8BymVfdeYsFPnIQFL8tEvcRXTKUE,47356
@@ -369,8 +369,8 @@ nucliadb/writer/tus/local.py,sha256=7jYa_w9b-N90jWgN2sQKkNcomqn6JMVBOVeDOVYJHto,
369
369
  nucliadb/writer/tus/s3.py,sha256=vF0NkFTXiXhXq3bCVXXVV-ED38ECVoUeeYViP8uMqcU,8357
370
370
  nucliadb/writer/tus/storage.py,sha256=ToqwjoYnjI4oIcwzkhha_MPxi-k4Jk3Lt55zRwaC1SM,2903
371
371
  nucliadb/writer/tus/utils.py,sha256=MSdVbRsRSZVdkaum69_0wku7X3p5wlZf4nr6E0GMKbw,2556
372
- nucliadb-6.4.0.post4204.dist-info/METADATA,sha256=fAIY46KkkEIlOfObcPGceV3ZnO74SMottRW6kUPOFnU,4223
373
- nucliadb-6.4.0.post4204.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
374
- nucliadb-6.4.0.post4204.dist-info/entry_points.txt,sha256=XqGfgFDuY3zXQc8ewXM2TRVjTModIq851zOsgrmaXx4,1268
375
- nucliadb-6.4.0.post4204.dist-info/top_level.txt,sha256=hwYhTVnX7jkQ9gJCkVrbqEG1M4lT2F_iPQND1fCzF80,20
376
- nucliadb-6.4.0.post4204.dist-info/RECORD,,
372
+ nucliadb-6.4.0.post4210.dist-info/METADATA,sha256=SB9gIMgWxoWNtUEexRLH85E0PL-MnroGhJ6aOambTT4,4223
373
+ nucliadb-6.4.0.post4210.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
374
+ nucliadb-6.4.0.post4210.dist-info/entry_points.txt,sha256=XqGfgFDuY3zXQc8ewXM2TRVjTModIq851zOsgrmaXx4,1268
375
+ nucliadb-6.4.0.post4210.dist-info/top_level.txt,sha256=hwYhTVnX7jkQ9gJCkVrbqEG1M4lT2F_iPQND1fCzF80,20
376
+ nucliadb-6.4.0.post4210.dist-info/RECORD,,