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.
- hyperforge_nucliadb/__init__.py +13 -0
- hyperforge_nucliadb/advanced_ask_agent.py +267 -0
- hyperforge_nucliadb/advanced_ask_config.py +241 -0
- hyperforge_nucliadb/ask/__init__.py +3 -0
- hyperforge_nucliadb/ask/analysis.py +310 -0
- hyperforge_nucliadb/ask/ask.py +326 -0
- hyperforge_nucliadb/ask/config.py +52 -0
- hyperforge_nucliadb/ask/hydrate.py +485 -0
- hyperforge_nucliadb/ask/kb_analysis.py +48 -0
- hyperforge_nucliadb/ask/knowledge_scan.py +150 -0
- hyperforge_nucliadb/ask/models.py +59 -0
- hyperforge_nucliadb/ask/multi.py +146 -0
- hyperforge_nucliadb/ask/nucliadb.py +238 -0
- hyperforge_nucliadb/ask/prompt_analysis.py +50 -0
- hyperforge_nucliadb/ask/query_analysis.py +83 -0
- hyperforge_nucliadb/ask/rerank.py +45 -0
- hyperforge_nucliadb/ask/utils.py +36 -0
- hyperforge_nucliadb/ask_utils.py +235 -0
- hyperforge_nucliadb/basic_ask_agent.py +1812 -0
- hyperforge_nucliadb/basic_ask_config.py +42 -0
- hyperforge_nucliadb/driver.py +428 -0
- hyperforge_nucliadb/driver_config.py +42 -0
- hyperforge_nucliadb/sync/__init__.py +0 -0
- hyperforge_nucliadb/sync/agent.py +477 -0
- hyperforge_nucliadb/sync/config.py +12 -0
- hyperforge_nucliadb/sync/config_driver.py +22 -0
- hyperforge_nucliadb/sync/driver.py +148 -0
- hyperforge_nucliadb-1.0.0.post22.dist-info/METADATA +26 -0
- hyperforge_nucliadb-1.0.0.post22.dist-info/RECORD +31 -0
- hyperforge_nucliadb-1.0.0.post22.dist-info/WHEEL +5 -0
- hyperforge_nucliadb-1.0.0.post22.dist-info/top_level.txt +1 -0
|
@@ -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
|
+
)
|