mlrun 1.10.0rc18__py3-none-any.whl → 1.11.0rc16__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 mlrun might be problematic. Click here for more details.

Files changed (167) hide show
  1. mlrun/__init__.py +24 -3
  2. mlrun/__main__.py +0 -4
  3. mlrun/artifacts/dataset.py +2 -2
  4. mlrun/artifacts/document.py +6 -1
  5. mlrun/artifacts/llm_prompt.py +21 -15
  6. mlrun/artifacts/model.py +3 -3
  7. mlrun/artifacts/plots.py +1 -1
  8. mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
  9. mlrun/auth/nuclio.py +89 -0
  10. mlrun/auth/providers.py +429 -0
  11. mlrun/auth/utils.py +415 -0
  12. mlrun/common/constants.py +14 -0
  13. mlrun/common/model_monitoring/helpers.py +123 -0
  14. mlrun/common/runtimes/constants.py +28 -0
  15. mlrun/common/schemas/__init__.py +14 -3
  16. mlrun/common/schemas/alert.py +2 -2
  17. mlrun/common/schemas/api_gateway.py +3 -0
  18. mlrun/common/schemas/auth.py +12 -10
  19. mlrun/common/schemas/client_spec.py +4 -0
  20. mlrun/common/schemas/constants.py +25 -0
  21. mlrun/common/schemas/frontend_spec.py +1 -8
  22. mlrun/common/schemas/function.py +34 -0
  23. mlrun/common/schemas/hub.py +33 -20
  24. mlrun/common/schemas/model_monitoring/__init__.py +2 -1
  25. mlrun/common/schemas/model_monitoring/constants.py +12 -15
  26. mlrun/common/schemas/model_monitoring/functions.py +13 -4
  27. mlrun/common/schemas/model_monitoring/model_endpoints.py +11 -0
  28. mlrun/common/schemas/pipeline.py +1 -1
  29. mlrun/common/schemas/secret.py +17 -2
  30. mlrun/common/secrets.py +95 -1
  31. mlrun/common/types.py +10 -10
  32. mlrun/config.py +69 -19
  33. mlrun/data_types/infer.py +2 -2
  34. mlrun/datastore/__init__.py +12 -5
  35. mlrun/datastore/azure_blob.py +162 -47
  36. mlrun/datastore/base.py +274 -10
  37. mlrun/datastore/datastore.py +7 -2
  38. mlrun/datastore/datastore_profile.py +84 -22
  39. mlrun/datastore/model_provider/huggingface_provider.py +225 -41
  40. mlrun/datastore/model_provider/mock_model_provider.py +87 -0
  41. mlrun/datastore/model_provider/model_provider.py +206 -74
  42. mlrun/datastore/model_provider/openai_provider.py +226 -66
  43. mlrun/datastore/s3.py +39 -18
  44. mlrun/datastore/sources.py +1 -1
  45. mlrun/datastore/store_resources.py +4 -4
  46. mlrun/datastore/storeytargets.py +17 -12
  47. mlrun/datastore/targets.py +1 -1
  48. mlrun/datastore/utils.py +25 -6
  49. mlrun/datastore/v3io.py +1 -1
  50. mlrun/db/base.py +63 -32
  51. mlrun/db/httpdb.py +373 -153
  52. mlrun/db/nopdb.py +54 -21
  53. mlrun/errors.py +4 -2
  54. mlrun/execution.py +66 -25
  55. mlrun/feature_store/api.py +1 -1
  56. mlrun/feature_store/common.py +1 -1
  57. mlrun/feature_store/feature_vector_utils.py +1 -1
  58. mlrun/feature_store/steps.py +8 -6
  59. mlrun/frameworks/_common/utils.py +3 -3
  60. mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
  61. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
  62. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
  63. mlrun/frameworks/_ml_common/utils.py +2 -1
  64. mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
  65. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
  66. mlrun/frameworks/onnx/dataset.py +2 -1
  67. mlrun/frameworks/onnx/mlrun_interface.py +2 -1
  68. mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
  69. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
  70. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
  71. mlrun/frameworks/pytorch/utils.py +2 -1
  72. mlrun/frameworks/sklearn/metric.py +2 -1
  73. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
  74. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
  75. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
  76. mlrun/hub/__init__.py +52 -0
  77. mlrun/hub/base.py +142 -0
  78. mlrun/hub/module.py +172 -0
  79. mlrun/hub/step.py +113 -0
  80. mlrun/k8s_utils.py +105 -16
  81. mlrun/launcher/base.py +15 -7
  82. mlrun/launcher/local.py +4 -1
  83. mlrun/model.py +14 -4
  84. mlrun/model_monitoring/__init__.py +0 -1
  85. mlrun/model_monitoring/api.py +65 -28
  86. mlrun/model_monitoring/applications/__init__.py +1 -1
  87. mlrun/model_monitoring/applications/base.py +299 -128
  88. mlrun/model_monitoring/applications/context.py +2 -4
  89. mlrun/model_monitoring/controller.py +132 -58
  90. mlrun/model_monitoring/db/_schedules.py +38 -29
  91. mlrun/model_monitoring/db/_stats.py +6 -16
  92. mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
  93. mlrun/model_monitoring/db/tsdb/base.py +29 -9
  94. mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
  95. mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
  96. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
  97. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
  98. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
  99. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
  100. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
  101. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
  102. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
  103. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
  104. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
  105. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
  106. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
  107. mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
  108. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +20 -9
  109. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +235 -51
  110. mlrun/model_monitoring/features_drift_table.py +2 -1
  111. mlrun/model_monitoring/helpers.py +30 -6
  112. mlrun/model_monitoring/stream_processing.py +34 -28
  113. mlrun/model_monitoring/writer.py +224 -4
  114. mlrun/package/__init__.py +2 -1
  115. mlrun/platforms/__init__.py +0 -43
  116. mlrun/platforms/iguazio.py +8 -4
  117. mlrun/projects/operations.py +17 -11
  118. mlrun/projects/pipelines.py +2 -2
  119. mlrun/projects/project.py +187 -123
  120. mlrun/run.py +95 -21
  121. mlrun/runtimes/__init__.py +2 -186
  122. mlrun/runtimes/base.py +103 -25
  123. mlrun/runtimes/constants.py +225 -0
  124. mlrun/runtimes/daskjob.py +5 -2
  125. mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
  126. mlrun/runtimes/local.py +5 -2
  127. mlrun/runtimes/mounts.py +20 -2
  128. mlrun/runtimes/nuclio/__init__.py +12 -7
  129. mlrun/runtimes/nuclio/api_gateway.py +36 -6
  130. mlrun/runtimes/nuclio/application/application.py +339 -40
  131. mlrun/runtimes/nuclio/function.py +222 -72
  132. mlrun/runtimes/nuclio/serving.py +132 -42
  133. mlrun/runtimes/pod.py +213 -21
  134. mlrun/runtimes/utils.py +49 -9
  135. mlrun/secrets.py +99 -14
  136. mlrun/serving/__init__.py +2 -0
  137. mlrun/serving/remote.py +84 -11
  138. mlrun/serving/routers.py +26 -44
  139. mlrun/serving/server.py +138 -51
  140. mlrun/serving/serving_wrapper.py +6 -2
  141. mlrun/serving/states.py +997 -283
  142. mlrun/serving/steps.py +62 -0
  143. mlrun/serving/system_steps.py +149 -95
  144. mlrun/serving/v2_serving.py +9 -10
  145. mlrun/track/trackers/mlflow_tracker.py +29 -31
  146. mlrun/utils/helpers.py +292 -94
  147. mlrun/utils/http.py +9 -2
  148. mlrun/utils/notifications/notification/base.py +18 -0
  149. mlrun/utils/notifications/notification/git.py +3 -5
  150. mlrun/utils/notifications/notification/mail.py +39 -16
  151. mlrun/utils/notifications/notification/slack.py +2 -4
  152. mlrun/utils/notifications/notification/webhook.py +2 -5
  153. mlrun/utils/notifications/notification_pusher.py +3 -3
  154. mlrun/utils/version/version.json +2 -2
  155. mlrun/utils/version/version.py +3 -4
  156. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +63 -74
  157. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +161 -143
  158. mlrun/api/schemas/__init__.py +0 -259
  159. mlrun/db/auth_utils.py +0 -152
  160. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -344
  161. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
  162. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
  163. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1266
  164. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
  165. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
  166. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
  167. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,376 @@
1
+ # Copyright 2025 Iguazio
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from datetime import datetime, timedelta
16
+ from typing import Optional, Union
17
+
18
+ import pandas as pd
19
+ import v3io_frames.client
20
+
21
+ import mlrun
22
+ import mlrun.common.schemas.model_monitoring as mm_schemas
23
+ import mlrun.errors
24
+ import mlrun.model_monitoring.db.tsdb.timescaledb.timescaledb_schema as timescaledb_schema
25
+ import mlrun.utils
26
+ from mlrun.common.schemas.model_monitoring.model_endpoints import _MetricPoint
27
+ from mlrun.model_monitoring.db.tsdb.timescaledb.utils.timescaledb_dataframe_processor import (
28
+ TimescaleDBDataFrameProcessor,
29
+ )
30
+ from mlrun.model_monitoring.db.tsdb.timescaledb.utils.timescaledb_query_builder import (
31
+ TimescaleDBQueryBuilder,
32
+ )
33
+ from mlrun.model_monitoring.helpers import get_invocations_fqn
34
+
35
+
36
+ class TimescaleDBPredictionsQueries:
37
+ """
38
+ Query class containing predictions-related query methods for TimescaleDB.
39
+
40
+ Can be used as a mixin or standalone instance with proper initialization.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ project: Optional[str] = None,
46
+ connection=None,
47
+ pre_aggregate_manager=None,
48
+ tables: Optional[dict] = None,
49
+ ):
50
+ """
51
+ Initialize TimescaleDB predictions query handler.
52
+
53
+ :param project: Project name
54
+ :param connection: TimescaleDB connection instance
55
+ :param pre_aggregate_manager: PreAggregateManager instance
56
+ :param tables: Dictionary of table schemas
57
+ """
58
+ self.project = project
59
+ self._connection = connection
60
+ self._pre_aggregate_manager = pre_aggregate_manager
61
+ self.tables = tables
62
+
63
+ def read_predictions_impl(
64
+ self,
65
+ *,
66
+ endpoint_id: Optional[str] = None,
67
+ start: datetime,
68
+ end: datetime,
69
+ columns: Optional[list[str]] = None,
70
+ aggregation_window: Optional[str] = None,
71
+ agg_funcs: Optional[list[str]] = None,
72
+ limit: Optional[int] = None,
73
+ use_pre_aggregates: bool = True,
74
+ timestamp_column: Optional[str] = None,
75
+ ) -> pd.DataFrame:
76
+ """Read predictions data from TimescaleDB (predictions table) - returns DataFrame.
77
+
78
+ :param endpoint_id: Endpoint ID to filter by, or None to get all endpoints
79
+ :param start: Start time
80
+ :param end: End time
81
+ :param columns: Optional list of specific columns to return
82
+ :param aggregation_window: Optional aggregation window (e.g., "1h", "1d")
83
+ :param agg_funcs: Optional list of aggregation functions (e.g., ["avg", "max"])
84
+ :param limit: Optional limit on number of results
85
+ :param use_pre_aggregates: Whether to use pre-aggregates if available
86
+ :param timestamp_column: Optional timestamp column to use for time filtering
87
+ :return: DataFrame with predictions data
88
+ """
89
+ if (agg_funcs and not aggregation_window) or (
90
+ aggregation_window and not agg_funcs
91
+ ):
92
+ raise mlrun.errors.MLRunInvalidArgumentError(
93
+ "both or neither of `aggregation_window` and `agg_funcs` must be provided"
94
+ )
95
+
96
+ # Align times if aggregation window is provided
97
+ start, end = self._pre_aggregate_manager.align_time_range(
98
+ start, end, aggregation_window
99
+ )
100
+
101
+ # Check if we can use pre-aggregates
102
+ can_use_pre_aggregates = (
103
+ use_pre_aggregates
104
+ and self._pre_aggregate_manager.can_use_pre_aggregates(
105
+ interval=aggregation_window, agg_funcs=agg_funcs
106
+ )
107
+ )
108
+
109
+ table_schema = self.tables[mm_schemas.TimescaleDBTables.PREDICTIONS]
110
+ filter_query = TimescaleDBQueryBuilder.build_endpoint_filter(endpoint_id)
111
+
112
+ query = table_schema._get_records_query(
113
+ start=start,
114
+ end=end,
115
+ columns_to_filter=columns,
116
+ filter_query=filter_query,
117
+ interval=aggregation_window if can_use_pre_aggregates else None,
118
+ agg_funcs=agg_funcs if can_use_pre_aggregates else None,
119
+ limit=limit,
120
+ use_pre_aggregates=can_use_pre_aggregates,
121
+ timestamp_column=timestamp_column,
122
+ )
123
+
124
+ result = self._connection.run(query=query)
125
+ df = TimescaleDBDataFrameProcessor.from_query_result(result)
126
+
127
+ if not df.empty:
128
+ # Set up time index based on whether we used aggregation
129
+ if aggregation_window and can_use_pre_aggregates:
130
+ time_col = timescaledb_schema.TIME_BUCKET_COLUMN
131
+ else:
132
+ time_col = table_schema.time_column
133
+
134
+ if time_col in df.columns:
135
+ df[time_col] = pd.to_datetime(df[time_col])
136
+ df.set_index(time_col, inplace=True)
137
+
138
+ return df
139
+
140
+ def read_predictions(
141
+ self,
142
+ *,
143
+ endpoint_id: str,
144
+ start: datetime,
145
+ end: datetime,
146
+ aggregation_window: Optional[str] = None,
147
+ agg_funcs: Optional[list[str]] = None,
148
+ limit: Optional[int] = None,
149
+ use_pre_aggregates: bool = True,
150
+ ) -> Union[
151
+ mm_schemas.ModelEndpointMonitoringMetricValues,
152
+ mm_schemas.ModelEndpointMonitoringMetricNoData,
153
+ ]:
154
+ """Read predictions with optional pre-aggregate optimization."""
155
+
156
+ table_schema = self.tables[mm_schemas.TimescaleDBTables.PREDICTIONS]
157
+ columns = [
158
+ table_schema.time_column,
159
+ mm_schemas.EventFieldType.ESTIMATED_PREDICTION_COUNT,
160
+ ]
161
+
162
+ # Get raw DataFrame from read_predictions_impl
163
+ df = self.read_predictions_impl(
164
+ endpoint_id=endpoint_id,
165
+ start=start,
166
+ end=end,
167
+ columns=columns,
168
+ aggregation_window=aggregation_window,
169
+ agg_funcs=agg_funcs,
170
+ limit=limit,
171
+ use_pre_aggregates=use_pre_aggregates,
172
+ )
173
+
174
+ # Convert to domain objects
175
+ full_name = get_invocations_fqn(self.project)
176
+
177
+ if df.empty:
178
+ return TimescaleDBDataFrameProcessor.handle_empty_dataframe(full_name)
179
+
180
+ # Determine value column name based on whether aggregation was used
181
+ can_use_pre_aggregates = (
182
+ use_pre_aggregates
183
+ and aggregation_window
184
+ and agg_funcs
185
+ and self._pre_aggregate_manager.can_use_pre_aggregates(
186
+ interval=aggregation_window, agg_funcs=agg_funcs
187
+ )
188
+ )
189
+
190
+ if agg_funcs and can_use_pre_aggregates:
191
+ value_col = (
192
+ f"{agg_funcs[0]}_{mm_schemas.EventFieldType.ESTIMATED_PREDICTION_COUNT}"
193
+ )
194
+ else:
195
+ value_col = mm_schemas.EventFieldType.ESTIMATED_PREDICTION_COUNT
196
+
197
+ return mm_schemas.ModelEndpointMonitoringMetricValues(
198
+ full_name=full_name,
199
+ values=[
200
+ _MetricPoint(timestamp=timestamp, value=value)
201
+ for timestamp, value in zip(df.index, df[value_col])
202
+ ],
203
+ )
204
+
205
+ def get_last_request(
206
+ self,
207
+ endpoint_ids: Union[str, list[str]],
208
+ start: Optional[datetime] = None,
209
+ end: Optional[datetime] = None,
210
+ interval: Optional[str] = None,
211
+ ) -> pd.DataFrame:
212
+ """Get last request timestamp with optional pre-aggregate optimization."""
213
+
214
+ # Prepare time range and interval (no auto-determination since interval may be None)
215
+ start, end, interval = TimescaleDBQueryBuilder.prepare_time_range_and_interval(
216
+ self._pre_aggregate_manager,
217
+ start,
218
+ end,
219
+ interval,
220
+ auto_determine_interval=False,
221
+ )
222
+ use_pre_aggregates = self._pre_aggregate_manager.can_use_pre_aggregates(
223
+ interval=interval
224
+ )
225
+
226
+ table_schema = self.tables[mm_schemas.TimescaleDBTables.PREDICTIONS]
227
+ filter_query = TimescaleDBQueryBuilder.build_endpoint_filter(endpoint_ids)
228
+
229
+ if use_pre_aggregates:
230
+ # Calculate latest (MAX) timestamp and corresponding latency per endpoint
231
+ # Use subquery to get time-bucketed data, then MAX over those results
232
+ subquery = table_schema._get_records_query(
233
+ start=start,
234
+ end=end,
235
+ columns_to_filter=[
236
+ timescaledb_schema.TIME_BUCKET_COLUMN,
237
+ f"max_{table_schema.time_column}",
238
+ f"max_{mm_schemas.EventFieldType.LATENCY}",
239
+ mm_schemas.WriterEvent.ENDPOINT_ID,
240
+ ],
241
+ filter_query=filter_query,
242
+ agg_funcs=["max"],
243
+ interval=interval,
244
+ use_pre_aggregates=True,
245
+ )
246
+
247
+ # Use helper to build endpoint aggregation query
248
+ query = TimescaleDBQueryBuilder.build_endpoint_aggregation_query(
249
+ subquery=subquery,
250
+ aggregation_columns={
251
+ mm_schemas.EventFieldType.LAST_REQUEST: f"MAX(max_{table_schema.time_column})",
252
+ "last_latency": f"MAX(max_{mm_schemas.EventFieldType.LATENCY})",
253
+ },
254
+ )
255
+
256
+ result = self._connection.run(query=query)
257
+ df = TimescaleDBDataFrameProcessor.from_query_result(result)
258
+ else:
259
+ # Use PostgreSQL DISTINCT ON for raw data - most efficient approach
260
+ query = f"""
261
+ SELECT DISTINCT ON ({mm_schemas.WriterEvent.ENDPOINT_ID})
262
+ {mm_schemas.WriterEvent.ENDPOINT_ID} AS endpoint_id,
263
+ {table_schema.time_column} AS {mm_schemas.EventFieldType.LAST_REQUEST},
264
+ {mm_schemas.EventFieldType.LATENCY} AS last_latency
265
+ FROM {table_schema.full_name()}
266
+ WHERE {filter_query}
267
+ AND {table_schema.time_column} >= '{start}'
268
+ AND {table_schema.time_column} <= '{end}'
269
+ ORDER BY {mm_schemas.WriterEvent.ENDPOINT_ID}, {table_schema.time_column} DESC;
270
+ """
271
+
272
+ result = self._connection.run(query=query)
273
+ df = TimescaleDBDataFrameProcessor.from_query_result(result)
274
+
275
+ # Convert timestamp to proper format (common for both paths)
276
+ if not df.empty and mm_schemas.EventFieldType.LAST_REQUEST in df.columns:
277
+ df[mm_schemas.EventFieldType.LAST_REQUEST] = pd.to_datetime(
278
+ df[mm_schemas.EventFieldType.LAST_REQUEST], errors="coerce", utc=True
279
+ )
280
+
281
+ return df
282
+
283
+ def get_avg_latency(
284
+ self,
285
+ endpoint_ids: Union[str, list[str]],
286
+ start: Optional[datetime] = None,
287
+ end: Optional[datetime] = None,
288
+ get_raw: bool = False,
289
+ ) -> Union[pd.DataFrame, list[v3io_frames.client.RawFrame]]:
290
+ """Get average latency with automatic pre-aggregate optimization, returning single value per endpoint."""
291
+
292
+ # Convert single endpoint to list for consistent handling
293
+ if isinstance(endpoint_ids, str):
294
+ endpoint_ids = [endpoint_ids]
295
+
296
+ # Set default start time and get end time
297
+ start = start or (mlrun.utils.datetime_now() - timedelta(hours=24))
298
+ # Prepare time range with auto-determined interval
299
+ start, end, interval = TimescaleDBQueryBuilder.prepare_time_range_and_interval(
300
+ self._pre_aggregate_manager, start, end
301
+ )
302
+
303
+ table_schema = self.tables[mm_schemas.TimescaleDBTables.PREDICTIONS]
304
+ filter_query = TimescaleDBQueryBuilder.build_endpoint_filter(endpoint_ids)
305
+
306
+ def build_pre_agg_query():
307
+ # Calculate overall average in SQL across all time buckets
308
+ # Use subquery to get time-bucketed data, then AVG over those results
309
+ subquery = table_schema._get_records_query(
310
+ start=start,
311
+ end=end,
312
+ columns_to_filter=[
313
+ timescaledb_schema.TIME_BUCKET_COLUMN,
314
+ mm_schemas.ModelEndpointSchema.AVG_LATENCY,
315
+ mm_schemas.WriterEvent.ENDPOINT_ID,
316
+ ],
317
+ filter_query=filter_query,
318
+ agg_funcs=["avg"],
319
+ interval=interval,
320
+ use_pre_aggregates=True,
321
+ )
322
+
323
+ # Use helper to build endpoint aggregation query
324
+ return TimescaleDBQueryBuilder.build_endpoint_aggregation_query(
325
+ subquery=subquery,
326
+ aggregation_columns={
327
+ mm_schemas.ModelEndpointSchema.AVG_LATENCY: f"AVG({mm_schemas.ModelEndpointSchema.AVG_LATENCY})"
328
+ },
329
+ )
330
+
331
+ def build_raw_query():
332
+ # Single aggregated value across entire time range
333
+ columns = [
334
+ f"{mm_schemas.WriterEvent.ENDPOINT_ID} AS {mm_schemas.WriterEvent.ENDPOINT_ID}",
335
+ f"AVG({mm_schemas.EventFieldType.LATENCY}) AS {mm_schemas.ModelEndpointSchema.AVG_LATENCY}",
336
+ ]
337
+ group_by_columns = [mm_schemas.WriterEvent.ENDPOINT_ID]
338
+
339
+ # Add additional filter to exclude invalid latency values
340
+ latency_col = mm_schemas.EventFieldType.LATENCY
341
+ latency_filter = f"{latency_col} IS NOT NULL AND {latency_col} > 0"
342
+ enhanced_filter_query = (
343
+ f"{filter_query} AND {latency_filter}"
344
+ if filter_query
345
+ else latency_filter
346
+ )
347
+
348
+ return table_schema._get_records_query(
349
+ start=start,
350
+ end=end,
351
+ columns_to_filter=columns,
352
+ filter_query=enhanced_filter_query,
353
+ group_by=group_by_columns,
354
+ order_by=mm_schemas.WriterEvent.ENDPOINT_ID,
355
+ )
356
+
357
+ # Column mapping rules for results (both pre-agg and raw return same structure now)
358
+ column_mapping_rules = {
359
+ mm_schemas.ModelEndpointSchema.AVG_LATENCY: [
360
+ mm_schemas.ModelEndpointSchema.AVG_LATENCY,
361
+ "average_latency",
362
+ mm_schemas.EventFieldType.LATENCY,
363
+ ],
364
+ mm_schemas.WriterEvent.ENDPOINT_ID: [mm_schemas.WriterEvent.ENDPOINT_ID],
365
+ }
366
+
367
+ # Both queries now return single value per endpoint, no post-processing needed
368
+ return self._connection.execute_with_fallback(
369
+ self._pre_aggregate_manager,
370
+ build_pre_agg_query,
371
+ build_raw_query,
372
+ interval=interval,
373
+ agg_funcs=["avg"],
374
+ column_mapping_rules=column_mapping_rules,
375
+ debug_name="avg_latency",
376
+ )