hyperforge-nucliadb 1.0.0.post22__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.
@@ -0,0 +1,235 @@
1
+ from typing import Literal, Optional
2
+
3
+ from nucliadb_models import filters as ndb_filters
4
+ from nucliadb_models.labels import LABEL_QUERY_ALIASES as LABEL_ALIASES
5
+ from nucliadb_models.search import SyncAskResponse
6
+
7
+ from hyperforge import logger
8
+
9
+
10
+ def get_chunk_text(ask_response: SyncAskResponse, chunk_id: str) -> str:
11
+ ids = chunk_id.split("/")
12
+ resource_id = ids[0]
13
+ resource = ask_response.retrieval_results.resources[resource_id]
14
+ field_id = f"/{ids[1]}/{ids[2]}" if len(ids) > 2 else ""
15
+ try:
16
+ # Try to get the text from the main retrieval results first
17
+ return resource.fields[field_id].paragraphs[chunk_id].text
18
+ except KeyError:
19
+ # If not found, try to get it from the augmented context, as it may be a chunk that was augmented as part of the RAG strategies.
20
+ if ask_response.augmented_context is not None:
21
+ try:
22
+ return ask_response.augmented_context.paragraphs[chunk_id].text
23
+ except KeyError:
24
+ # If still not found, return an empty string
25
+ pass
26
+ return ""
27
+
28
+
29
+ def spit_by_filter_type(text: str) -> Optional[tuple[str, str]]:
30
+ """
31
+ Splits the filter type from the rest of the filter.
32
+ Examples:
33
+ /l/foo/bar -> ("classification.labels", "foo/bar")
34
+ /classification.labels/foo/bar -> ("classification.labels", "foo/bar")
35
+ /n/i/application/pdf -> ("icon", "application/pdf")
36
+ """
37
+ for alias, replacement in LABEL_ALIASES.items():
38
+ if text.startswith(replacement + "/"):
39
+ return alias, text[len(replacement) :]
40
+ if text.startswith(alias + "/"):
41
+ return alias, text[len(alias) :]
42
+ return None
43
+
44
+
45
+ def _convert_classification_labels(filter: str) -> ndb_filters.Label:
46
+ labelset, label = split_head_and_tail(filter)
47
+ return ndb_filters.Label(labelset=labelset, label=label)
48
+
49
+
50
+ def _convert_to_field_mimetype(filter: str) -> ndb_filters.FieldMimetype:
51
+ itype, isubtype = split_head_and_tail(filter)
52
+ return ndb_filters.FieldMimetype(
53
+ type=itype,
54
+ subtype=isubtype,
55
+ )
56
+
57
+
58
+ def _convert_to_language(filter_type: str, filter: str) -> ndb_filters.Language:
59
+ only_primary = "metadata.language" == filter_type
60
+ return ndb_filters.Language(language=filter, only_primary=only_primary)
61
+
62
+
63
+ def _convert_to_origin(
64
+ filter_type: str, filter: str
65
+ ) -> (
66
+ ndb_filters.OriginTag
67
+ | ndb_filters.OriginMetadata
68
+ | ndb_filters.OriginPath
69
+ | ndb_filters.OriginCollaborator
70
+ | ndb_filters.OriginSource
71
+ ):
72
+ if filter_type == "origin.tags":
73
+ return ndb_filters.OriginTag(tag=filter)
74
+ elif filter_type == "origin.path":
75
+ return ndb_filters.OriginPath(prefix=filter)
76
+ elif filter_type == "origin.metadata":
77
+ key, value = split_head_and_tail(filter)
78
+ return ndb_filters.OriginMetadata(field=key, value=value)
79
+ elif filter_type == "origin.collaborators":
80
+ return ndb_filters.OriginCollaborator(collaborator=filter)
81
+ elif filter_type == "origin.sources":
82
+ return ndb_filters.OriginSource(id=filter)
83
+ else:
84
+ raise ValueError(f"Unsupported origin filter type: {filter_type}")
85
+
86
+
87
+ def _convert_to_entities(filter: str) -> ndb_filters.Entity:
88
+ entity_type, entity_value = split_head_and_tail(filter)
89
+ return ndb_filters.Entity(subtype=entity_type, value=entity_value)
90
+
91
+
92
+ def _convert_to_field(filter: str) -> ndb_filters.Field:
93
+ ftype, fname = split_head_and_tail(filter)
94
+ return ndb_filters.Field(type=ftype, name=fname)
95
+
96
+
97
+ def to_field_filter_expression(
98
+ filter: str,
99
+ ) -> Optional[ndb_filters.FieldFilterExpression]:
100
+ """
101
+ Converts plain string labels to FieldFilterExpression objects
102
+ Examples:
103
+ /l/foo/bar -> Label(labelset="foo", label="bar")
104
+ /n/i/application/pdf -> FieldMimetype(type="application", subtype="pdf")
105
+ """
106
+ parts = spit_by_filter_type(filter.lstrip("/"))
107
+ if parts is None:
108
+ logger.error(f"Could not parse filter: {filter}")
109
+ return None
110
+ filter_type, filter = parts
111
+ if filter_type == "classification.labels":
112
+ return _convert_classification_labels(filter)
113
+ elif filter_type == "icon":
114
+ return _convert_to_field_mimetype(filter)
115
+ elif filter_type in ("metadata.language", "metadata.languages"):
116
+ return _convert_to_language(filter_type, filter)
117
+ elif filter_type in (
118
+ "origin.tags",
119
+ "origin.path",
120
+ "origin.metadata",
121
+ "origin.collaborators",
122
+ "origin.sources",
123
+ ):
124
+ return _convert_to_origin(filter_type, filter)
125
+ elif filter_type == "entities":
126
+ return _convert_to_entities(filter)
127
+ elif filter_type == "field":
128
+ return _convert_to_field(filter)
129
+ elif filter_type == "generated.data-augmentation":
130
+ return ndb_filters.Generated(by="data-augmentation")
131
+ else:
132
+ logger.error(f"Unhandled filter format: {filter}")
133
+ return None
134
+
135
+
136
+ def to_resource_filter_expression(
137
+ filter: str,
138
+ ) -> Optional[ndb_filters.ResourceFilterExpression]:
139
+ """
140
+ Converts plain string labels to ResourceFilterExpression objects
141
+ Examples:
142
+ /l/foo/bar -> Label(labelset="foo", label="bar")
143
+ /n/i/application/pdf -> ResourceMimetype(type="application", subtype="pdf")
144
+ """
145
+ parts = spit_by_filter_type(filter.lstrip("/"))
146
+ if parts is None:
147
+ logger.error(f"Could not parse filter: {filter}")
148
+ return None
149
+ filter_type, filter = parts
150
+ if filter_type == "classification.labels":
151
+ return _convert_classification_labels(filter)
152
+ elif filter_type == "icon":
153
+ itype, isubtype = split_head_and_tail(filter)
154
+ return ndb_filters.ResourceMimetype(
155
+ type=itype,
156
+ subtype=isubtype,
157
+ )
158
+ elif filter_type in ("metadata.language", "metadata.languages"):
159
+ return _convert_to_language(filter_type, filter)
160
+ elif filter_type in (
161
+ "origin.tags",
162
+ "origin.path",
163
+ "origin.metadata",
164
+ "origin.collaborators",
165
+ "origin.sources",
166
+ ):
167
+ return _convert_to_origin(filter_type, filter)
168
+ else:
169
+ logger.error(f"Unhandled filter format: {filter}")
170
+ return None
171
+
172
+
173
+ def split_head_and_tail(text: str) -> tuple[str, Optional[str]]:
174
+ parts = text.lstrip("/").split("/", maxsplit=1)
175
+ head = parts[0]
176
+ tail = None
177
+ if len(parts) > 1:
178
+ tail = parts[1]
179
+ return head, tail
180
+
181
+
182
+ def combine_filter_expressions(
183
+ expressions: list[ndb_filters.FilterExpression],
184
+ operator: Literal["and", "or"] = "and",
185
+ ) -> ndb_filters.FilterExpression:
186
+ """
187
+ Merge the two filter expressions into a single expression.
188
+ """
189
+ if len(expressions) == 0:
190
+ raise ValueError("At least one filter expression is required to combine")
191
+ elif len(expressions) == 1:
192
+ return expressions[0]
193
+ # Make sure all expressions have the same operator
194
+ one = expressions[0]
195
+ for other in expressions[1:]:
196
+ if one.operator != other.operator:
197
+ raise ValueError(
198
+ "Cannot combine filter expressions with different operators"
199
+ )
200
+
201
+ operator_klass = ndb_filters.And if operator == "and" else ndb_filters.Or
202
+ result = ndb_filters.FilterExpression(
203
+ operator=one.operator,
204
+ )
205
+ field_expressions = [expr.field for expr in expressions if expr.field is not None]
206
+ if len(field_expressions) > 1:
207
+ result.field = operator_klass(operands=field_expressions)
208
+ elif len(field_expressions) == 1:
209
+ result.field = field_expressions[0]
210
+ paragraph_expressions = [
211
+ expr.paragraph for expr in expressions if expr.paragraph is not None
212
+ ]
213
+ if len(paragraph_expressions) > 1:
214
+ result.paragraph = operator_klass(operands=paragraph_expressions)
215
+ elif len(paragraph_expressions) == 1:
216
+ result.paragraph = paragraph_expressions[0]
217
+ return result
218
+
219
+
220
+ def combine_catalog_filter_expressions(
221
+ expressions: list[ndb_filters.CatalogFilterExpression],
222
+ operator: Literal["and", "or"] = "and",
223
+ ) -> ndb_filters.CatalogFilterExpression:
224
+ """
225
+ Merge the two catalog filter expressions.
226
+ """
227
+ if len(expressions) == 0:
228
+ raise ValueError("At least one filter expression is required to combine")
229
+ elif len(expressions) == 1:
230
+ return expressions[0]
231
+ else:
232
+ operator_klass = ndb_filters.And if operator == "and" else ndb_filters.Or
233
+ return ndb_filters.CatalogFilterExpression(
234
+ resource=operator_klass(operands=[expr.resource for expr in expressions])
235
+ )