nucliadb 6.7.2.post4874__py3-none-any.whl → 6.10.0.post5705__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.
- migrations/0023_backfill_pg_catalog.py +8 -4
- migrations/0028_extracted_vectors_reference.py +1 -1
- migrations/0029_backfill_field_status.py +3 -4
- migrations/0032_remove_old_relations.py +2 -3
- migrations/0038_backfill_catalog_field_labels.py +8 -4
- migrations/0039_backfill_converation_splits_metadata.py +106 -0
- migrations/0040_migrate_search_configurations.py +79 -0
- migrations/0041_reindex_conversations.py +137 -0
- migrations/pg/0010_shards_index.py +34 -0
- nucliadb/search/api/v1/resource/utils.py → migrations/pg/0011_catalog_statistics.py +5 -6
- migrations/pg/0012_catalog_statistics_undo.py +26 -0
- nucliadb/backups/create.py +2 -15
- nucliadb/backups/restore.py +4 -15
- nucliadb/backups/tasks.py +4 -1
- nucliadb/common/back_pressure/cache.py +2 -3
- nucliadb/common/back_pressure/materializer.py +7 -13
- nucliadb/common/back_pressure/settings.py +6 -6
- nucliadb/common/back_pressure/utils.py +1 -0
- nucliadb/common/cache.py +9 -9
- nucliadb/common/catalog/__init__.py +79 -0
- nucliadb/common/catalog/dummy.py +36 -0
- nucliadb/common/catalog/interface.py +85 -0
- nucliadb/{search/search/pgcatalog.py → common/catalog/pg.py} +330 -232
- nucliadb/common/catalog/utils.py +56 -0
- nucliadb/common/cluster/manager.py +8 -23
- nucliadb/common/cluster/rebalance.py +484 -112
- nucliadb/common/cluster/rollover.py +36 -9
- nucliadb/common/cluster/settings.py +4 -9
- nucliadb/common/cluster/utils.py +34 -8
- nucliadb/common/context/__init__.py +7 -8
- nucliadb/common/context/fastapi.py +1 -2
- nucliadb/common/datamanagers/__init__.py +2 -4
- nucliadb/common/datamanagers/atomic.py +9 -2
- nucliadb/common/datamanagers/cluster.py +1 -2
- nucliadb/common/datamanagers/fields.py +3 -4
- nucliadb/common/datamanagers/kb.py +6 -6
- nucliadb/common/datamanagers/labels.py +2 -3
- nucliadb/common/datamanagers/resources.py +10 -33
- nucliadb/common/datamanagers/rollover.py +5 -7
- nucliadb/common/datamanagers/search_configurations.py +1 -2
- nucliadb/common/datamanagers/synonyms.py +1 -2
- nucliadb/common/datamanagers/utils.py +4 -4
- nucliadb/common/datamanagers/vectorsets.py +4 -4
- nucliadb/common/external_index_providers/base.py +32 -5
- nucliadb/common/external_index_providers/manager.py +5 -34
- nucliadb/common/external_index_providers/settings.py +1 -27
- nucliadb/common/filter_expression.py +129 -41
- nucliadb/common/http_clients/exceptions.py +8 -0
- nucliadb/common/http_clients/processing.py +16 -23
- nucliadb/common/http_clients/utils.py +3 -0
- nucliadb/common/ids.py +82 -58
- nucliadb/common/locking.py +1 -2
- nucliadb/common/maindb/driver.py +9 -8
- nucliadb/common/maindb/local.py +5 -5
- nucliadb/common/maindb/pg.py +9 -8
- nucliadb/common/nidx.py +22 -5
- nucliadb/common/vector_index_config.py +1 -1
- nucliadb/export_import/datamanager.py +4 -3
- nucliadb/export_import/exporter.py +11 -19
- nucliadb/export_import/importer.py +13 -6
- nucliadb/export_import/tasks.py +2 -0
- nucliadb/export_import/utils.py +6 -18
- nucliadb/health.py +2 -2
- nucliadb/ingest/app.py +8 -8
- nucliadb/ingest/consumer/consumer.py +8 -10
- nucliadb/ingest/consumer/pull.py +10 -8
- nucliadb/ingest/consumer/service.py +5 -30
- nucliadb/ingest/consumer/shard_creator.py +16 -5
- nucliadb/ingest/consumer/utils.py +1 -1
- nucliadb/ingest/fields/base.py +37 -49
- nucliadb/ingest/fields/conversation.py +55 -9
- nucliadb/ingest/fields/exceptions.py +1 -2
- nucliadb/ingest/fields/file.py +22 -8
- nucliadb/ingest/fields/link.py +7 -7
- nucliadb/ingest/fields/text.py +2 -3
- nucliadb/ingest/orm/brain_v2.py +89 -57
- nucliadb/ingest/orm/broker_message.py +2 -4
- nucliadb/ingest/orm/entities.py +10 -209
- nucliadb/ingest/orm/index_message.py +128 -113
- nucliadb/ingest/orm/knowledgebox.py +91 -59
- nucliadb/ingest/orm/processor/auditing.py +1 -3
- nucliadb/ingest/orm/processor/data_augmentation.py +1 -2
- nucliadb/ingest/orm/processor/processor.py +98 -153
- nucliadb/ingest/orm/processor/sequence_manager.py +1 -2
- nucliadb/ingest/orm/resource.py +82 -71
- nucliadb/ingest/orm/utils.py +1 -1
- nucliadb/ingest/partitions.py +12 -1
- nucliadb/ingest/processing.py +17 -17
- nucliadb/ingest/serialize.py +202 -145
- nucliadb/ingest/service/writer.py +15 -114
- nucliadb/ingest/settings.py +36 -15
- nucliadb/ingest/utils.py +1 -2
- nucliadb/learning_proxy.py +23 -26
- nucliadb/metrics_exporter.py +20 -6
- nucliadb/middleware/__init__.py +82 -1
- nucliadb/migrator/datamanager.py +4 -11
- nucliadb/migrator/migrator.py +1 -2
- nucliadb/migrator/models.py +1 -2
- nucliadb/migrator/settings.py +1 -2
- nucliadb/models/internal/augment.py +614 -0
- nucliadb/models/internal/processing.py +19 -19
- nucliadb/openapi.py +2 -2
- nucliadb/purge/__init__.py +3 -8
- nucliadb/purge/orphan_shards.py +1 -2
- nucliadb/reader/__init__.py +5 -0
- nucliadb/reader/api/models.py +6 -13
- nucliadb/reader/api/v1/download.py +59 -38
- nucliadb/reader/api/v1/export_import.py +4 -4
- nucliadb/reader/api/v1/knowledgebox.py +37 -9
- nucliadb/reader/api/v1/learning_config.py +33 -14
- nucliadb/reader/api/v1/resource.py +61 -9
- nucliadb/reader/api/v1/services.py +18 -14
- nucliadb/reader/app.py +3 -1
- nucliadb/reader/reader/notifications.py +1 -2
- nucliadb/search/api/v1/__init__.py +3 -0
- nucliadb/search/api/v1/ask.py +3 -4
- nucliadb/search/api/v1/augment.py +585 -0
- nucliadb/search/api/v1/catalog.py +15 -19
- nucliadb/search/api/v1/find.py +16 -22
- nucliadb/search/api/v1/hydrate.py +328 -0
- nucliadb/search/api/v1/knowledgebox.py +1 -2
- nucliadb/search/api/v1/predict_proxy.py +1 -2
- nucliadb/search/api/v1/resource/ask.py +28 -8
- nucliadb/search/api/v1/resource/ingestion_agents.py +5 -6
- nucliadb/search/api/v1/resource/search.py +9 -11
- nucliadb/search/api/v1/retrieve.py +130 -0
- nucliadb/search/api/v1/search.py +28 -32
- nucliadb/search/api/v1/suggest.py +11 -14
- nucliadb/search/api/v1/summarize.py +1 -2
- nucliadb/search/api/v1/utils.py +2 -2
- nucliadb/search/app.py +3 -2
- nucliadb/search/augmentor/__init__.py +21 -0
- nucliadb/search/augmentor/augmentor.py +232 -0
- nucliadb/search/augmentor/fields.py +704 -0
- nucliadb/search/augmentor/metrics.py +24 -0
- nucliadb/search/augmentor/paragraphs.py +334 -0
- nucliadb/search/augmentor/resources.py +238 -0
- nucliadb/search/augmentor/utils.py +33 -0
- nucliadb/search/lifecycle.py +3 -1
- nucliadb/search/predict.py +33 -19
- nucliadb/search/predict_models.py +8 -9
- nucliadb/search/requesters/utils.py +11 -10
- nucliadb/search/search/cache.py +19 -42
- nucliadb/search/search/chat/ask.py +131 -59
- nucliadb/search/search/chat/exceptions.py +3 -5
- nucliadb/search/search/chat/fetcher.py +201 -0
- nucliadb/search/search/chat/images.py +6 -4
- nucliadb/search/search/chat/old_prompt.py +1375 -0
- nucliadb/search/search/chat/parser.py +510 -0
- nucliadb/search/search/chat/prompt.py +563 -615
- nucliadb/search/search/chat/query.py +453 -32
- nucliadb/search/search/chat/rpc.py +85 -0
- nucliadb/search/search/fetch.py +3 -4
- nucliadb/search/search/filters.py +8 -11
- nucliadb/search/search/find.py +33 -31
- nucliadb/search/search/find_merge.py +124 -331
- nucliadb/search/search/graph_strategy.py +14 -12
- nucliadb/search/search/hydrator/__init__.py +49 -0
- nucliadb/search/search/hydrator/fields.py +217 -0
- nucliadb/search/search/hydrator/images.py +130 -0
- nucliadb/search/search/hydrator/paragraphs.py +323 -0
- nucliadb/search/search/hydrator/resources.py +60 -0
- nucliadb/search/search/ingestion_agents.py +5 -5
- nucliadb/search/search/merge.py +90 -94
- nucliadb/search/search/metrics.py +24 -7
- nucliadb/search/search/paragraphs.py +7 -9
- nucliadb/search/search/predict_proxy.py +44 -18
- nucliadb/search/search/query.py +14 -86
- nucliadb/search/search/query_parser/fetcher.py +51 -82
- nucliadb/search/search/query_parser/models.py +19 -48
- nucliadb/search/search/query_parser/old_filters.py +20 -19
- nucliadb/search/search/query_parser/parsers/ask.py +5 -6
- nucliadb/search/search/query_parser/parsers/catalog.py +7 -11
- nucliadb/search/search/query_parser/parsers/common.py +21 -13
- nucliadb/search/search/query_parser/parsers/find.py +6 -29
- nucliadb/search/search/query_parser/parsers/graph.py +18 -28
- nucliadb/search/search/query_parser/parsers/retrieve.py +207 -0
- nucliadb/search/search/query_parser/parsers/search.py +15 -56
- nucliadb/search/search/query_parser/parsers/unit_retrieval.py +8 -29
- nucliadb/search/search/rank_fusion.py +18 -13
- nucliadb/search/search/rerankers.py +6 -7
- nucliadb/search/search/retrieval.py +300 -0
- nucliadb/search/search/summarize.py +5 -6
- nucliadb/search/search/utils.py +3 -4
- nucliadb/search/settings.py +1 -2
- nucliadb/standalone/api_router.py +1 -1
- nucliadb/standalone/app.py +4 -3
- nucliadb/standalone/auth.py +5 -6
- nucliadb/standalone/lifecycle.py +2 -2
- nucliadb/standalone/run.py +5 -4
- nucliadb/standalone/settings.py +5 -6
- nucliadb/standalone/versions.py +3 -4
- nucliadb/tasks/consumer.py +13 -8
- nucliadb/tasks/models.py +2 -1
- nucliadb/tasks/producer.py +3 -3
- nucliadb/tasks/retries.py +8 -7
- nucliadb/train/api/utils.py +1 -3
- nucliadb/train/api/v1/shards.py +1 -2
- nucliadb/train/api/v1/trainset.py +1 -2
- nucliadb/train/app.py +1 -1
- nucliadb/train/generator.py +4 -4
- nucliadb/train/generators/field_classifier.py +2 -2
- nucliadb/train/generators/field_streaming.py +6 -6
- nucliadb/train/generators/image_classifier.py +2 -2
- nucliadb/train/generators/paragraph_classifier.py +2 -2
- nucliadb/train/generators/paragraph_streaming.py +2 -2
- nucliadb/train/generators/question_answer_streaming.py +2 -2
- nucliadb/train/generators/sentence_classifier.py +4 -10
- nucliadb/train/generators/token_classifier.py +3 -2
- nucliadb/train/generators/utils.py +6 -5
- nucliadb/train/nodes.py +3 -3
- nucliadb/train/resource.py +6 -8
- nucliadb/train/settings.py +3 -4
- nucliadb/train/types.py +11 -11
- nucliadb/train/upload.py +3 -2
- nucliadb/train/uploader.py +1 -2
- nucliadb/train/utils.py +1 -2
- nucliadb/writer/api/v1/export_import.py +4 -1
- nucliadb/writer/api/v1/field.py +15 -14
- nucliadb/writer/api/v1/knowledgebox.py +18 -56
- nucliadb/writer/api/v1/learning_config.py +5 -4
- nucliadb/writer/api/v1/resource.py +9 -20
- nucliadb/writer/api/v1/services.py +10 -132
- nucliadb/writer/api/v1/upload.py +73 -72
- nucliadb/writer/app.py +8 -2
- nucliadb/writer/resource/basic.py +12 -15
- nucliadb/writer/resource/field.py +43 -5
- nucliadb/writer/resource/origin.py +7 -0
- nucliadb/writer/settings.py +2 -3
- nucliadb/writer/tus/__init__.py +2 -3
- nucliadb/writer/tus/azure.py +5 -7
- nucliadb/writer/tus/dm.py +3 -3
- nucliadb/writer/tus/exceptions.py +3 -4
- nucliadb/writer/tus/gcs.py +15 -22
- nucliadb/writer/tus/s3.py +2 -3
- nucliadb/writer/tus/storage.py +3 -3
- {nucliadb-6.7.2.post4874.dist-info → nucliadb-6.10.0.post5705.dist-info}/METADATA +10 -11
- nucliadb-6.10.0.post5705.dist-info/RECORD +410 -0
- nucliadb/common/datamanagers/entities.py +0 -139
- nucliadb/common/external_index_providers/pinecone.py +0 -894
- nucliadb/ingest/orm/processor/pgcatalog.py +0 -129
- nucliadb/search/search/hydrator.py +0 -197
- nucliadb-6.7.2.post4874.dist-info/RECORD +0 -383
- {nucliadb-6.7.2.post4874.dist-info → nucliadb-6.10.0.post5705.dist-info}/WHEEL +0 -0
- {nucliadb-6.7.2.post4874.dist-info → nucliadb-6.10.0.post5705.dist-info}/entry_points.txt +0 -0
- {nucliadb-6.7.2.post4874.dist-info → nucliadb-6.10.0.post5705.dist-info}/top_level.txt +0 -0
|
@@ -20,18 +20,22 @@
|
|
|
20
20
|
import dataclasses
|
|
21
21
|
import functools
|
|
22
22
|
import json
|
|
23
|
-
from
|
|
23
|
+
from collections.abc import AsyncGenerator
|
|
24
|
+
from typing import cast
|
|
24
25
|
|
|
25
26
|
from nuclia_models.common.consumption import Consumption
|
|
26
27
|
from nuclia_models.predict.generative_responses import (
|
|
27
28
|
CitationsGenerativeResponse,
|
|
29
|
+
FootnoteCitationsGenerativeResponse,
|
|
28
30
|
GenerativeChunk,
|
|
29
31
|
JSONGenerativeResponse,
|
|
30
32
|
MetaGenerativeResponse,
|
|
33
|
+
ReasoningGenerativeResponse,
|
|
31
34
|
StatusGenerativeResponse,
|
|
32
35
|
TextGenerativeResponse,
|
|
33
36
|
)
|
|
34
37
|
from pydantic_core import ValidationError
|
|
38
|
+
from typing_extensions import assert_never
|
|
35
39
|
|
|
36
40
|
from nucliadb.common.datamanagers.exceptions import KnowledgeBoxNotFound
|
|
37
41
|
from nucliadb.common.exceptions import InvalidQueryError
|
|
@@ -47,11 +51,13 @@ from nucliadb.search.search.chat.exceptions import (
|
|
|
47
51
|
AnswerJsonSchemaTooLong,
|
|
48
52
|
NoRetrievalResultsError,
|
|
49
53
|
)
|
|
54
|
+
from nucliadb.search.search.chat.old_prompt import PromptContextBuilder as OldPromptContextBuilder
|
|
50
55
|
from nucliadb.search.search.chat.prompt import PromptContextBuilder
|
|
51
56
|
from nucliadb.search.search.chat.query import (
|
|
52
57
|
NOT_ENOUGH_CONTEXT_ANSWER,
|
|
53
58
|
ChatAuditor,
|
|
54
59
|
add_resource_filter,
|
|
60
|
+
get_answer_stream,
|
|
55
61
|
get_find_results,
|
|
56
62
|
get_relations_results,
|
|
57
63
|
maybe_audit_chat,
|
|
@@ -67,11 +73,15 @@ from nucliadb.search.search.metrics import AskMetrics, Metrics
|
|
|
67
73
|
from nucliadb.search.search.query_parser.fetcher import Fetcher
|
|
68
74
|
from nucliadb.search.search.query_parser.parsers.ask import fetcher_for_ask, parse_ask
|
|
69
75
|
from nucliadb.search.search.rank_fusion import WeightedCombSum
|
|
70
|
-
from
|
|
71
|
-
|
|
76
|
+
from nucliadb_models.retrieval import (
|
|
77
|
+
GraphScore,
|
|
78
|
+
KeywordScore,
|
|
79
|
+
RerankerScore,
|
|
80
|
+
RrfScore,
|
|
81
|
+
SemanticScore,
|
|
72
82
|
)
|
|
73
|
-
from nucliadb.search.utilities import get_predict
|
|
74
83
|
from nucliadb_models.search import (
|
|
84
|
+
SCORE_TYPE,
|
|
75
85
|
AnswerAskResponseItem,
|
|
76
86
|
AskRequest,
|
|
77
87
|
AskResponseItem,
|
|
@@ -90,6 +100,7 @@ from nucliadb_models.search import (
|
|
|
90
100
|
FindOptions,
|
|
91
101
|
FindParagraph,
|
|
92
102
|
FindRequest,
|
|
103
|
+
FootnoteCitationsAskResponseItem,
|
|
93
104
|
GraphStrategy,
|
|
94
105
|
JSONAskResponseItem,
|
|
95
106
|
KnowledgeboxFindResults,
|
|
@@ -102,6 +113,7 @@ from nucliadb_models.search import (
|
|
|
102
113
|
PromptContext,
|
|
103
114
|
PromptContextOrder,
|
|
104
115
|
RagStrategyName,
|
|
116
|
+
ReasoningAskResponseItem,
|
|
105
117
|
Relations,
|
|
106
118
|
RelationsAskResponseItem,
|
|
107
119
|
RetrievalAskResponseItem,
|
|
@@ -114,7 +126,9 @@ from nucliadb_models.search import (
|
|
|
114
126
|
parse_rephrase_prompt,
|
|
115
127
|
)
|
|
116
128
|
from nucliadb_telemetry import errors
|
|
129
|
+
from nucliadb_utils import const
|
|
117
130
|
from nucliadb_utils.exceptions import LimitsExceededError
|
|
131
|
+
from nucliadb_utils.utilities import has_feature
|
|
118
132
|
|
|
119
133
|
|
|
120
134
|
@dataclasses.dataclass
|
|
@@ -128,7 +142,7 @@ class RetrievalResults:
|
|
|
128
142
|
main_query: KnowledgeboxFindResults
|
|
129
143
|
fetcher: Fetcher
|
|
130
144
|
main_query_weight: float
|
|
131
|
-
prequeries:
|
|
145
|
+
prequeries: list[PreQueryResult] | None = None
|
|
132
146
|
best_matches: list[RetrievalMatch] = dataclasses.field(default_factory=list)
|
|
133
147
|
|
|
134
148
|
|
|
@@ -139,15 +153,15 @@ class AskResult:
|
|
|
139
153
|
kbid: str,
|
|
140
154
|
ask_request: AskRequest,
|
|
141
155
|
main_results: KnowledgeboxFindResults,
|
|
142
|
-
prequeries_results:
|
|
143
|
-
nuclia_learning_id:
|
|
144
|
-
predict_answer_stream:
|
|
156
|
+
prequeries_results: list[PreQueryResult] | None,
|
|
157
|
+
nuclia_learning_id: str | None,
|
|
158
|
+
predict_answer_stream: AsyncGenerator[GenerativeChunk, None] | None,
|
|
145
159
|
prompt_context: PromptContext,
|
|
146
160
|
prompt_context_order: PromptContextOrder,
|
|
147
161
|
auditor: ChatAuditor,
|
|
148
162
|
metrics: AskMetrics,
|
|
149
163
|
best_matches: list[RetrievalMatch],
|
|
150
|
-
debug_chat_model:
|
|
164
|
+
debug_chat_model: ChatModel | None,
|
|
151
165
|
augmented_context: AugmentedContext,
|
|
152
166
|
):
|
|
153
167
|
# Initial attributes
|
|
@@ -167,12 +181,14 @@ class AskResult:
|
|
|
167
181
|
|
|
168
182
|
# Computed from the predict chat answer stream
|
|
169
183
|
self._answer_text = ""
|
|
170
|
-
self.
|
|
171
|
-
self.
|
|
172
|
-
self.
|
|
173
|
-
self.
|
|
174
|
-
self.
|
|
175
|
-
self.
|
|
184
|
+
self._reasoning_text: str | None = None
|
|
185
|
+
self._object: JSONGenerativeResponse | None = None
|
|
186
|
+
self._status: StatusGenerativeResponse | None = None
|
|
187
|
+
self._citations: CitationsGenerativeResponse | None = None
|
|
188
|
+
self._footnote_citations: FootnoteCitationsGenerativeResponse | None = None
|
|
189
|
+
self._metadata: MetaGenerativeResponse | None = None
|
|
190
|
+
self._relations: Relations | None = None
|
|
191
|
+
self._consumption: Consumption | None = None
|
|
176
192
|
|
|
177
193
|
@property
|
|
178
194
|
def status_code(self) -> AnswerStatusCode:
|
|
@@ -181,7 +197,7 @@ class AskResult:
|
|
|
181
197
|
return AnswerStatusCode(self._status.code)
|
|
182
198
|
|
|
183
199
|
@property
|
|
184
|
-
def status_error_details(self) ->
|
|
200
|
+
def status_error_details(self) -> str | None:
|
|
185
201
|
if self._status is None: # pragma: no cover
|
|
186
202
|
return None
|
|
187
203
|
return self._status.details
|
|
@@ -220,12 +236,21 @@ class AskResult:
|
|
|
220
236
|
async def _stream(self) -> AsyncGenerator[AskResponseItemType, None]:
|
|
221
237
|
# First, stream out the predict answer
|
|
222
238
|
first_chunk_yielded = False
|
|
239
|
+
first_reasoning_chunk_yielded = False
|
|
223
240
|
with self.metrics.time("stream_predict_answer"):
|
|
224
241
|
async for answer_chunk in self._stream_predict_answer_text():
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
242
|
+
if isinstance(answer_chunk, TextGenerativeResponse):
|
|
243
|
+
yield AnswerAskResponseItem(text=answer_chunk.text)
|
|
244
|
+
if not first_chunk_yielded:
|
|
245
|
+
self.metrics.record_first_chunk_yielded()
|
|
246
|
+
first_chunk_yielded = True
|
|
247
|
+
elif isinstance(answer_chunk, ReasoningGenerativeResponse):
|
|
248
|
+
yield ReasoningAskResponseItem(text=answer_chunk.text)
|
|
249
|
+
if not first_reasoning_chunk_yielded:
|
|
250
|
+
self.metrics.record_first_reasoning_chunk_yielded()
|
|
251
|
+
first_reasoning_chunk_yielded = True
|
|
252
|
+
else:
|
|
253
|
+
assert_never(answer_chunk)
|
|
229
254
|
|
|
230
255
|
if self._object is not None:
|
|
231
256
|
yield JSONAskResponseItem(object=self._object.object)
|
|
@@ -274,8 +299,10 @@ class AskResult:
|
|
|
274
299
|
audit_answer = json.dumps(self._object.object).encode("utf-8")
|
|
275
300
|
self.auditor.audit(
|
|
276
301
|
text_answer=audit_answer,
|
|
302
|
+
text_reasoning=self._reasoning_text,
|
|
277
303
|
generative_answer_time=self.metrics["stream_predict_answer"],
|
|
278
304
|
generative_answer_first_chunk_time=self.metrics.get_first_chunk_time() or 0,
|
|
305
|
+
generative_reasoning_first_chunk_time=self.metrics.get_first_reasoning_chunk_time(),
|
|
279
306
|
rephrase_time=self.metrics.get("rephrase"),
|
|
280
307
|
status_code=self.status_code,
|
|
281
308
|
)
|
|
@@ -287,6 +314,11 @@ class AskResult:
|
|
|
287
314
|
yield CitationsAskResponseItem(
|
|
288
315
|
citations=self._citations.citations,
|
|
289
316
|
)
|
|
317
|
+
# Stream out the footnote citations mapping
|
|
318
|
+
if self._footnote_citations is not None:
|
|
319
|
+
yield FootnoteCitationsAskResponseItem(
|
|
320
|
+
footnote_to_context=self._footnote_citations.footnote_to_context,
|
|
321
|
+
)
|
|
290
322
|
|
|
291
323
|
# Stream out generic metadata about the answer
|
|
292
324
|
if self._metadata is not None:
|
|
@@ -364,11 +396,15 @@ class AskResult:
|
|
|
364
396
|
if self._citations is not None:
|
|
365
397
|
citations = self._citations.citations
|
|
366
398
|
|
|
399
|
+
footnote_citations = {}
|
|
400
|
+
if self._footnote_citations is not None:
|
|
401
|
+
footnote_citations = self._footnote_citations.footnote_to_context
|
|
402
|
+
|
|
367
403
|
answer_json = None
|
|
368
404
|
if self._object is not None:
|
|
369
405
|
answer_json = self._object.object
|
|
370
406
|
|
|
371
|
-
prequeries_results:
|
|
407
|
+
prequeries_results: dict[str, KnowledgeboxFindResults] | None = None
|
|
372
408
|
if self.prequeries_results:
|
|
373
409
|
prequeries_results = {}
|
|
374
410
|
for index, (prequery, result) in enumerate(self.prequeries_results):
|
|
@@ -384,6 +420,7 @@ class AskResult:
|
|
|
384
420
|
|
|
385
421
|
response = SyncAskResponse(
|
|
386
422
|
answer=self._answer_text,
|
|
423
|
+
reasoning=self._reasoning_text,
|
|
387
424
|
answer_json=answer_json,
|
|
388
425
|
status=self.status_code.prettify(),
|
|
389
426
|
relations=self._relations,
|
|
@@ -391,6 +428,7 @@ class AskResult:
|
|
|
391
428
|
retrieval_best_matches=best_matches,
|
|
392
429
|
prequeries=prequeries_results,
|
|
393
430
|
citations=citations,
|
|
431
|
+
citation_footnote_to_context=footnote_citations,
|
|
394
432
|
metadata=metadata,
|
|
395
433
|
consumption=self._consumption,
|
|
396
434
|
learning_id=self.nuclia_learning_id or "",
|
|
@@ -420,7 +458,9 @@ class AskResult:
|
|
|
420
458
|
)
|
|
421
459
|
return self._relations
|
|
422
460
|
|
|
423
|
-
async def _stream_predict_answer_text(
|
|
461
|
+
async def _stream_predict_answer_text(
|
|
462
|
+
self,
|
|
463
|
+
) -> AsyncGenerator[TextGenerativeResponse | ReasoningGenerativeResponse, None]:
|
|
424
464
|
"""
|
|
425
465
|
Reads the stream of the generative model, yielding the answer text but also parsing
|
|
426
466
|
other items like status codes, citations and miscellaneous metadata.
|
|
@@ -435,13 +475,21 @@ class AskResult:
|
|
|
435
475
|
item = generative_chunk.chunk
|
|
436
476
|
if isinstance(item, TextGenerativeResponse):
|
|
437
477
|
self._answer_text += item.text
|
|
438
|
-
yield item
|
|
478
|
+
yield item
|
|
479
|
+
elif isinstance(item, ReasoningGenerativeResponse):
|
|
480
|
+
if self._reasoning_text is None:
|
|
481
|
+
self._reasoning_text = item.text
|
|
482
|
+
else:
|
|
483
|
+
self._reasoning_text += item.text
|
|
484
|
+
yield item
|
|
439
485
|
elif isinstance(item, JSONGenerativeResponse):
|
|
440
486
|
self._object = item
|
|
441
487
|
elif isinstance(item, StatusGenerativeResponse):
|
|
442
488
|
self._status = item
|
|
443
489
|
elif isinstance(item, CitationsGenerativeResponse):
|
|
444
490
|
self._citations = item
|
|
491
|
+
elif isinstance(item, FootnoteCitationsGenerativeResponse):
|
|
492
|
+
self._footnote_citations = item
|
|
445
493
|
elif isinstance(item, MetaGenerativeResponse):
|
|
446
494
|
self._metadata = item
|
|
447
495
|
elif isinstance(item, Consumption):
|
|
@@ -456,8 +504,8 @@ class AskResult:
|
|
|
456
504
|
class NotEnoughContextAskResult(AskResult):
|
|
457
505
|
def __init__(
|
|
458
506
|
self,
|
|
459
|
-
main_results:
|
|
460
|
-
prequeries_results:
|
|
507
|
+
main_results: KnowledgeboxFindResults | None = None,
|
|
508
|
+
prequeries_results: list[PreQueryResult] | None = None,
|
|
461
509
|
):
|
|
462
510
|
self.main_results = main_results or KnowledgeboxFindResults(resources={}, min_score=None)
|
|
463
511
|
self.prequeries_results = prequeries_results or []
|
|
@@ -507,8 +555,8 @@ async def ask(
|
|
|
507
555
|
user_id: str,
|
|
508
556
|
client_type: NucliaDBClientType,
|
|
509
557
|
origin: str,
|
|
510
|
-
resource:
|
|
511
|
-
extra_predict_headers:
|
|
558
|
+
resource: str | None = None,
|
|
559
|
+
extra_predict_headers: dict[str, str] | None = None,
|
|
512
560
|
) -> AskResult:
|
|
513
561
|
metrics = AskMetrics()
|
|
514
562
|
chat_history = ask_request.chat_history or []
|
|
@@ -559,11 +607,13 @@ async def ask(
|
|
|
559
607
|
origin=origin,
|
|
560
608
|
generative_answer_time=0,
|
|
561
609
|
generative_answer_first_chunk_time=0,
|
|
610
|
+
generative_reasoning_first_chunk_time=None,
|
|
562
611
|
rephrase_time=metrics.get("rephrase"),
|
|
563
612
|
user_query=user_query,
|
|
564
613
|
rephrased_query=rephrased_query,
|
|
565
614
|
retrieval_rephrase_query=err.main_query.rephrased_query if err.main_query else None,
|
|
566
615
|
text_answer=b"",
|
|
616
|
+
text_reasoning=None,
|
|
567
617
|
status_code=AnswerStatusCode.NO_RETRIEVAL_DATA,
|
|
568
618
|
chat_history=chat_history,
|
|
569
619
|
query_context={},
|
|
@@ -585,19 +635,36 @@ async def ask(
|
|
|
585
635
|
|
|
586
636
|
# Now we build the prompt context
|
|
587
637
|
with metrics.time("context_building"):
|
|
588
|
-
prompt_context_builder
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
638
|
+
prompt_context_builder: PromptContextBuilder | OldPromptContextBuilder
|
|
639
|
+
if has_feature(const.Features.ASK_DECOUPLED, context={"kbid": kbid}):
|
|
640
|
+
prompt_context_builder = PromptContextBuilder(
|
|
641
|
+
kbid=kbid,
|
|
642
|
+
ordered_paragraphs=[match.paragraph for match in retrieval_results.best_matches],
|
|
643
|
+
resource=resource,
|
|
644
|
+
user_context=user_context,
|
|
645
|
+
user_image_context=ask_request.extra_context_images,
|
|
646
|
+
strategies=ask_request.rag_strategies,
|
|
647
|
+
image_strategies=ask_request.rag_images_strategies,
|
|
648
|
+
max_context_characters=tokens_to_chars(generation.max_context_tokens),
|
|
649
|
+
visual_llm=generation.use_visual_llm,
|
|
650
|
+
query_image=ask_request.query_image,
|
|
651
|
+
metrics=metrics.child_span("context_building"),
|
|
652
|
+
)
|
|
653
|
+
else:
|
|
654
|
+
prompt_context_builder = OldPromptContextBuilder(
|
|
655
|
+
kbid=kbid,
|
|
656
|
+
ordered_paragraphs=[match.paragraph for match in retrieval_results.best_matches],
|
|
657
|
+
resource=resource,
|
|
658
|
+
user_context=user_context,
|
|
659
|
+
user_image_context=ask_request.extra_context_images,
|
|
660
|
+
strategies=ask_request.rag_strategies,
|
|
661
|
+
image_strategies=ask_request.rag_images_strategies,
|
|
662
|
+
max_context_characters=tokens_to_chars(generation.max_context_tokens),
|
|
663
|
+
visual_llm=generation.use_visual_llm,
|
|
664
|
+
query_image=ask_request.query_image,
|
|
665
|
+
metrics=metrics.child_span("context_building"),
|
|
666
|
+
)
|
|
667
|
+
|
|
601
668
|
(
|
|
602
669
|
prompt_context,
|
|
603
670
|
prompt_context_order,
|
|
@@ -625,6 +692,7 @@ async def ask(
|
|
|
625
692
|
json_schema=ask_request.answer_json_schema,
|
|
626
693
|
rerank_context=False,
|
|
627
694
|
top_k=ask_request.top_k,
|
|
695
|
+
reasoning=ask_request.reasoning,
|
|
628
696
|
)
|
|
629
697
|
|
|
630
698
|
nuclia_learning_id = None
|
|
@@ -632,14 +700,11 @@ async def ask(
|
|
|
632
700
|
predict_answer_stream = None
|
|
633
701
|
if ask_request.generate_answer:
|
|
634
702
|
with metrics.time("stream_start"):
|
|
635
|
-
predict = get_predict()
|
|
636
703
|
(
|
|
637
704
|
nuclia_learning_id,
|
|
638
705
|
nuclia_learning_model,
|
|
639
706
|
predict_answer_stream,
|
|
640
|
-
) = await
|
|
641
|
-
kbid=kbid, item=chat_model, extra_headers=extra_predict_headers
|
|
642
|
-
)
|
|
707
|
+
) = await get_answer_stream(kbid=kbid, item=chat_model, extra_headers=extra_predict_headers)
|
|
643
708
|
|
|
644
709
|
auditor = ChatAuditor(
|
|
645
710
|
kbid=kbid,
|
|
@@ -714,7 +779,7 @@ def handled_ask_exceptions(func):
|
|
|
714
779
|
return wrapper
|
|
715
780
|
|
|
716
781
|
|
|
717
|
-
def parse_prequeries(ask_request: AskRequest) ->
|
|
782
|
+
def parse_prequeries(ask_request: AskRequest) -> PreQueriesStrategy | None:
|
|
718
783
|
query_ids = []
|
|
719
784
|
for rag_strategy in ask_request.rag_strategies:
|
|
720
785
|
if rag_strategy.name == RagStrategyName.PREQUERIES:
|
|
@@ -733,7 +798,7 @@ def parse_prequeries(ask_request: AskRequest) -> Optional[PreQueriesStrategy]:
|
|
|
733
798
|
return None
|
|
734
799
|
|
|
735
800
|
|
|
736
|
-
def parse_graph_strategy(ask_request: AskRequest) ->
|
|
801
|
+
def parse_graph_strategy(ask_request: AskRequest) -> GraphStrategy | None:
|
|
737
802
|
for rag_strategy in ask_request.rag_strategies:
|
|
738
803
|
if rag_strategy.name == RagStrategyName.GRAPH:
|
|
739
804
|
return cast(GraphStrategy, rag_strategy)
|
|
@@ -748,7 +813,7 @@ async def retrieval_step(
|
|
|
748
813
|
user_id: str,
|
|
749
814
|
origin: str,
|
|
750
815
|
metrics: Metrics,
|
|
751
|
-
resource:
|
|
816
|
+
resource: str | None = None,
|
|
752
817
|
) -> RetrievalResults:
|
|
753
818
|
"""
|
|
754
819
|
This function encapsulates all the logic related to retrieval in the ask endpoint.
|
|
@@ -787,7 +852,7 @@ async def retrieval_in_kb(
|
|
|
787
852
|
) -> RetrievalResults:
|
|
788
853
|
prequeries = parse_prequeries(ask_request)
|
|
789
854
|
graph_strategy = parse_graph_strategy(ask_request)
|
|
790
|
-
main_results, prequeries_results,
|
|
855
|
+
main_results, prequeries_results, fetcher, reranker = await get_find_results(
|
|
791
856
|
kbid=kbid,
|
|
792
857
|
query=main_query,
|
|
793
858
|
item=ask_request,
|
|
@@ -799,10 +864,6 @@ async def retrieval_in_kb(
|
|
|
799
864
|
)
|
|
800
865
|
|
|
801
866
|
if graph_strategy is not None:
|
|
802
|
-
assert parsed_query.retrieval.reranker is not None, (
|
|
803
|
-
"find parser must provide a reranking algorithm"
|
|
804
|
-
)
|
|
805
|
-
reranker = get_reranker(parsed_query.retrieval.reranker)
|
|
806
867
|
graph_results, graph_request = await get_graph_results(
|
|
807
868
|
kbid=kbid,
|
|
808
869
|
query=main_query,
|
|
@@ -835,7 +896,7 @@ async def retrieval_in_kb(
|
|
|
835
896
|
return RetrievalResults(
|
|
836
897
|
main_query=main_results,
|
|
837
898
|
prequeries=prequeries_results,
|
|
838
|
-
fetcher=
|
|
899
|
+
fetcher=fetcher,
|
|
839
900
|
main_query_weight=main_query_weight,
|
|
840
901
|
best_matches=best_matches,
|
|
841
902
|
)
|
|
@@ -875,7 +936,7 @@ async def retrieval_in_resource(
|
|
|
875
936
|
)
|
|
876
937
|
add_resource_filter(prequery.request, [resource])
|
|
877
938
|
|
|
878
|
-
main_results, prequeries_results,
|
|
939
|
+
main_results, prequeries_results, fetcher, _ = await get_find_results(
|
|
879
940
|
kbid=kbid,
|
|
880
941
|
query=main_query,
|
|
881
942
|
item=ask_request,
|
|
@@ -898,7 +959,7 @@ async def retrieval_in_resource(
|
|
|
898
959
|
return RetrievalResults(
|
|
899
960
|
main_query=main_results,
|
|
900
961
|
prequeries=prequeries_results,
|
|
901
|
-
fetcher=
|
|
962
|
+
fetcher=fetcher,
|
|
902
963
|
main_query_weight=main_query_weight,
|
|
903
964
|
best_matches=best_matches,
|
|
904
965
|
)
|
|
@@ -910,7 +971,7 @@ class _FindParagraph(ScoredTextBlock):
|
|
|
910
971
|
|
|
911
972
|
def compute_best_matches(
|
|
912
973
|
main_results: KnowledgeboxFindResults,
|
|
913
|
-
prequeries_results:
|
|
974
|
+
prequeries_results: list[PreQueryResult] | None = None,
|
|
914
975
|
main_query_weight: float = 1.0,
|
|
915
976
|
) -> list[RetrievalMatch]:
|
|
916
977
|
"""
|
|
@@ -925,15 +986,27 @@ def compute_best_matches(
|
|
|
925
986
|
`main_query_weight` is the weight given to the paragraphs matching the main query when calculating the final score.
|
|
926
987
|
"""
|
|
927
988
|
|
|
989
|
+
score_type_map = {
|
|
990
|
+
SCORE_TYPE.VECTOR: SemanticScore,
|
|
991
|
+
SCORE_TYPE.BM25: KeywordScore,
|
|
992
|
+
SCORE_TYPE.BOTH: RrfScore, # /find only exposes RRF as rank fusion algorithm
|
|
993
|
+
SCORE_TYPE.RERANKER: RerankerScore,
|
|
994
|
+
SCORE_TYPE.RELATION_RELEVANCE: GraphScore,
|
|
995
|
+
}
|
|
996
|
+
|
|
928
997
|
def extract_paragraphs(results: KnowledgeboxFindResults) -> list[_FindParagraph]:
|
|
929
998
|
paragraphs = []
|
|
930
999
|
for resource in results.resources.values():
|
|
931
1000
|
for field in resource.fields.values():
|
|
932
1001
|
for paragraph in field.paragraphs.values():
|
|
1002
|
+
# TODO(decoupled-ask): we don't know the score history, as
|
|
1003
|
+
# we are using find results. Once we move boolean queries
|
|
1004
|
+
# inside the new retrieval flow we'll move this and have the
|
|
1005
|
+
# proper information to do this rank fusion
|
|
933
1006
|
paragraphs.append(
|
|
934
1007
|
_FindParagraph(
|
|
935
1008
|
paragraph_id=ParagraphId.from_string(paragraph.id),
|
|
936
|
-
score=paragraph.score,
|
|
1009
|
+
scores=[score_type_map[paragraph.score_type](score=paragraph.score)],
|
|
937
1010
|
score_type=paragraph.score_type,
|
|
938
1011
|
original=paragraph,
|
|
939
1012
|
)
|
|
@@ -969,7 +1042,7 @@ def compute_best_matches(
|
|
|
969
1042
|
|
|
970
1043
|
def calculate_prequeries_for_json_schema(
|
|
971
1044
|
ask_request: AskRequest,
|
|
972
|
-
) ->
|
|
1045
|
+
) -> PreQueriesStrategy | None:
|
|
973
1046
|
"""
|
|
974
1047
|
This function generates a PreQueriesStrategy with a query for each property in the JSON schema
|
|
975
1048
|
found in ask_request.answer_json_schema.
|
|
@@ -1034,7 +1107,6 @@ def calculate_prequeries_for_json_schema(
|
|
|
1034
1107
|
rephrase=ask_request.rephrase,
|
|
1035
1108
|
rephrase_prompt=parse_rephrase_prompt(ask_request),
|
|
1036
1109
|
security=ask_request.security,
|
|
1037
|
-
autofilter=False,
|
|
1038
1110
|
)
|
|
1039
1111
|
prequery = PreQuery(
|
|
1040
1112
|
request=req,
|
|
@@ -19,17 +19,15 @@
|
|
|
19
19
|
#
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
from typing import Optional
|
|
23
|
-
|
|
24
22
|
from nucliadb_models.search import KnowledgeboxFindResults, PreQueryResult
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
class NoRetrievalResultsError(Exception):
|
|
28
26
|
def __init__(
|
|
29
27
|
self,
|
|
30
|
-
main:
|
|
31
|
-
prequeries:
|
|
32
|
-
prefilters:
|
|
28
|
+
main: KnowledgeboxFindResults | None = None,
|
|
29
|
+
prequeries: list[PreQueryResult] | None = None,
|
|
30
|
+
prefilters: list[PreQueryResult] | None = None,
|
|
33
31
|
):
|
|
34
32
|
self.main_query = main
|
|
35
33
|
self.prequeries = prequeries
|