datarobot-moderations 11.1.12__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.
- datarobot_dome/__init__.py +11 -0
- datarobot_dome/async_http_client.py +248 -0
- datarobot_dome/chat_helper.py +227 -0
- datarobot_dome/constants.py +318 -0
- datarobot_dome/drum_integration.py +977 -0
- datarobot_dome/guard.py +736 -0
- datarobot_dome/guard_executor.py +755 -0
- datarobot_dome/guard_helpers.py +457 -0
- datarobot_dome/guards/__init__.py +11 -0
- datarobot_dome/guards/guard_llm_mixin.py +232 -0
- datarobot_dome/llm.py +148 -0
- datarobot_dome/metrics/__init__.py +11 -0
- datarobot_dome/metrics/citation_metrics.py +98 -0
- datarobot_dome/metrics/factory.py +52 -0
- datarobot_dome/metrics/metric_scorer.py +78 -0
- datarobot_dome/pipeline/__init__.py +11 -0
- datarobot_dome/pipeline/llm_pipeline.py +474 -0
- datarobot_dome/pipeline/pipeline.py +376 -0
- datarobot_dome/pipeline/vdb_pipeline.py +127 -0
- datarobot_dome/streaming.py +395 -0
- datarobot_moderations-11.1.12.dist-info/METADATA +113 -0
- datarobot_moderations-11.1.12.dist-info/RECORD +23 -0
- datarobot_moderations-11.1.12.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# ---------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2025 DataRobot, Inc. and its affiliates. All rights reserved.
|
|
3
|
+
# Last updated 2025.
|
|
4
|
+
#
|
|
5
|
+
# DataRobot, Inc. Confidential.
|
|
6
|
+
# This is proprietary source code of DataRobot, Inc. and its affiliates.
|
|
7
|
+
#
|
|
8
|
+
# This file and its contents are subject to DataRobot Tool and Utility Agreement.
|
|
9
|
+
# For details, see
|
|
10
|
+
# https://www.datarobot.com/wp-content/uploads/2021/07/DataRobot-Tool-and-Utility-Agreement.pdf.
|
|
11
|
+
# ---------------------------------------------------------------------------------
|
|
12
|
+
import asyncio
|
|
13
|
+
import logging
|
|
14
|
+
import math
|
|
15
|
+
import os
|
|
16
|
+
import traceback
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from datetime import timezone
|
|
19
|
+
|
|
20
|
+
import datarobot as dr
|
|
21
|
+
import numpy as np
|
|
22
|
+
from datarobot.errors import ClientError
|
|
23
|
+
from datarobot.mlops.events import MLOpsEvent
|
|
24
|
+
from datarobot.models.deployment import CustomMetric
|
|
25
|
+
|
|
26
|
+
from datarobot_dome.async_http_client import AsyncHTTPClient
|
|
27
|
+
from datarobot_dome.constants import DEFAULT_GUARD_PREDICTION_TIMEOUT_IN_SEC
|
|
28
|
+
from datarobot_dome.constants import LOGGER_NAME_PREFIX
|
|
29
|
+
from datarobot_dome.constants import ModerationEventTypes
|
|
30
|
+
|
|
31
|
+
CUSTOM_METRICS_BULK_UPLOAD_API_PREFIX = "deployments"
|
|
32
|
+
CUSTOM_METRICS_BULK_UPLOAD_API_SUFFIX = "customMetrics/bulkUpload/"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Pipeline:
|
|
36
|
+
common_message = "Custom Metrics and deployment settings will not be available"
|
|
37
|
+
|
|
38
|
+
def __init__(self, async_http_timeout_sec=DEFAULT_GUARD_PREDICTION_TIMEOUT_IN_SEC):
|
|
39
|
+
self._logger = logging.getLogger(LOGGER_NAME_PREFIX + "." + self.__class__.__name__)
|
|
40
|
+
self.custom_metric = {}
|
|
41
|
+
self._deployment = None
|
|
42
|
+
self._association_id_column_name = None
|
|
43
|
+
self._datarobot_url = None
|
|
44
|
+
self._datarobot_api_token = None
|
|
45
|
+
self.dr_client = None
|
|
46
|
+
self._headers = None
|
|
47
|
+
self._deployment_id = None
|
|
48
|
+
self._model_id = None
|
|
49
|
+
self.async_http_client = None
|
|
50
|
+
self._custom_metrics_bulk_upload_url = None
|
|
51
|
+
self._assoc_id_specific_custom_metric_ids = list()
|
|
52
|
+
self.aggregate_custom_metric = None
|
|
53
|
+
self.custom_metric_map = dict()
|
|
54
|
+
# List of custom metrics names which do not need the association id while reporting
|
|
55
|
+
self.custom_metrics_no_association_ids = list()
|
|
56
|
+
self.delayed_custom_metric_creation = False
|
|
57
|
+
self.upload_custom_metrics_tasks = set()
|
|
58
|
+
|
|
59
|
+
self.create_dr_client()
|
|
60
|
+
|
|
61
|
+
if self._datarobot_url and self._datarobot_api_token:
|
|
62
|
+
self.async_http_client = AsyncHTTPClient(async_http_timeout_sec)
|
|
63
|
+
|
|
64
|
+
def create_dr_client(self):
|
|
65
|
+
if self.dr_client:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# This URL and Token is where the custom LLM model is running.
|
|
69
|
+
self._datarobot_url = os.environ.get("DATAROBOT_ENDPOINT", None)
|
|
70
|
+
if self._datarobot_url is None:
|
|
71
|
+
self._logger.warning(f"Missing DataRobot endpoint, {self.common_message}")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
self._datarobot_api_token = os.environ.get("DATAROBOT_API_TOKEN", None)
|
|
75
|
+
if self._datarobot_api_token is None:
|
|
76
|
+
self._logger.warning(f"Missing DataRobot API Token, {self.common_message}")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
# This is regular / default DataRobot Client
|
|
80
|
+
self.dr_client = dr.Client(endpoint=self._datarobot_url, token=self._datarobot_api_token)
|
|
81
|
+
self._headers = {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
"Authorization": f"Bearer {self._datarobot_api_token}",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def _query_self_deployment(self):
|
|
87
|
+
"""
|
|
88
|
+
Query the details of the deployment (LLM) that this pipeline is running
|
|
89
|
+
moderations for
|
|
90
|
+
:return:
|
|
91
|
+
"""
|
|
92
|
+
self._deployment_id = os.environ.get("MLOPS_DEPLOYMENT_ID", None)
|
|
93
|
+
if self._deployment_id is None:
|
|
94
|
+
self._logger.warning(f'Custom Model workshop "test" mode?, {self.common_message}')
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
# Get the model id from environ variable, because moderation lib cannot
|
|
98
|
+
# query deployment each time there is scoring data.
|
|
99
|
+
self._model_id = os.environ.get("MLOPS_MODEL_ID", None)
|
|
100
|
+
self._logger.info(f"Model ID from env variable {self._model_id}")
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
self._deployment = dr.Deployment.get(deployment_id=self._deployment_id)
|
|
104
|
+
self._logger.info(f"Model ID set on the deployment {self._deployment.model['id']}")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
self._logger.warning(
|
|
107
|
+
f"Couldn't query the deployment Exception: {e}, {self.common_message}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def _query_association_id_column_name(self):
|
|
111
|
+
self._logger.info(f"Deployment ID: {self._deployment_id}")
|
|
112
|
+
if self._deployment is None:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
self._logger.info(f"Check Association ID Column name: {self._association_id_column_name}")
|
|
116
|
+
# Apparently, the pipeline.init() is called only once when deployment is created.
|
|
117
|
+
# If the association id column name is not set (we cannot set it during creation
|
|
118
|
+
# of the deployment), moderation library never gets it. So, moderation library
|
|
119
|
+
# is going to query it one time during prediction request
|
|
120
|
+
if self._association_id_column_name:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
association_id_settings = self._deployment.get_association_id_settings()
|
|
125
|
+
self._logger.debug(f"Association id settings: {association_id_settings}")
|
|
126
|
+
column_names = association_id_settings.get("column_names")
|
|
127
|
+
if column_names and len(column_names) > 0:
|
|
128
|
+
self._association_id_column_name = column_names[0]
|
|
129
|
+
self.auto_generate_association_ids = association_id_settings.get(
|
|
130
|
+
"auto_generate_id", False
|
|
131
|
+
)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
self._logger.warning(
|
|
134
|
+
f"Couldn't query the association id settings, "
|
|
135
|
+
f"custom metrics will not be available {e}"
|
|
136
|
+
)
|
|
137
|
+
self._logger.error(traceback.format_exc())
|
|
138
|
+
|
|
139
|
+
if self._association_id_column_name is None:
|
|
140
|
+
self._logger.warning(
|
|
141
|
+
"Association ID column is not set on the deployment, "
|
|
142
|
+
"data quality analysis will not be available"
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
self._logger.info(f"Association ID column name: {self._association_id_column_name}")
|
|
146
|
+
|
|
147
|
+
def create_custom_metrics_if_any(self):
|
|
148
|
+
"""
|
|
149
|
+
Over-arching function to create the custom-metrics in the DR app for the deployment.
|
|
150
|
+
Provides some protection for inactive deployments.
|
|
151
|
+
"""
|
|
152
|
+
self._query_self_deployment()
|
|
153
|
+
if self._deployment is None:
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
self._custom_metrics_bulk_upload_url = (
|
|
157
|
+
CUSTOM_METRICS_BULK_UPLOAD_API_PREFIX
|
|
158
|
+
+ "/"
|
|
159
|
+
+ self._deployment_id
|
|
160
|
+
+ "/"
|
|
161
|
+
+ CUSTOM_METRICS_BULK_UPLOAD_API_SUFFIX
|
|
162
|
+
)
|
|
163
|
+
self._logger.info(f"URL: {self._custom_metrics_bulk_upload_url}")
|
|
164
|
+
|
|
165
|
+
if self._deployment.status == "inactive":
|
|
166
|
+
self.delayed_custom_metric_creation = True
|
|
167
|
+
self._logger.warning("Deployment is not active, delaying custom metric creation")
|
|
168
|
+
else:
|
|
169
|
+
self._logger.info("Deployment is active, creating custom metrics")
|
|
170
|
+
self.create_custom_metrics()
|
|
171
|
+
self.delayed_custom_metric_creation = False
|
|
172
|
+
|
|
173
|
+
def create_custom_metrics(self):
|
|
174
|
+
"""
|
|
175
|
+
Creates all the custom-metrics in the DR app for an active deployment.
|
|
176
|
+
|
|
177
|
+
The `custom_metric_map` and `_requires_association_id` attributes are consulted to
|
|
178
|
+
insure the appropriate data is put in place for reporting.
|
|
179
|
+
"""
|
|
180
|
+
cleanup_metrics_list = list()
|
|
181
|
+
for index, (metric_name, custom_metric) in enumerate(self.custom_metric_map.items()):
|
|
182
|
+
metric_definition = custom_metric["metric_definition"]
|
|
183
|
+
try:
|
|
184
|
+
# We create metrics one by one, instead of using a library call. This gives
|
|
185
|
+
# us control over which are duplicates, if max limit reached etc and we can
|
|
186
|
+
# take appropriate actions accordingly. Performance wise it is same, because
|
|
187
|
+
# library also runs a loop to create custom metrics one by one
|
|
188
|
+
_metric_obj = CustomMetric.create(
|
|
189
|
+
deployment_id=self._deployment_id,
|
|
190
|
+
name=metric_name,
|
|
191
|
+
directionality=metric_definition["directionality"],
|
|
192
|
+
aggregation_type=metric_definition["type"],
|
|
193
|
+
time_step=metric_definition["timeStep"],
|
|
194
|
+
units=metric_definition["units"],
|
|
195
|
+
baseline_value=metric_definition["baselineValue"],
|
|
196
|
+
is_model_specific=metric_definition["isModelSpecific"],
|
|
197
|
+
)
|
|
198
|
+
custom_metric["id"] = _metric_obj.id
|
|
199
|
+
custom_metric["requires_association_id"] = self._requires_association_id(
|
|
200
|
+
metric_name
|
|
201
|
+
)
|
|
202
|
+
except ClientError as e:
|
|
203
|
+
if e.status_code == 409:
|
|
204
|
+
if "not unique for deployment" in e.json["message"]:
|
|
205
|
+
# Duplicate entry nothing to worry - just continue
|
|
206
|
+
self._logger.error(f"Metric '{metric_name}' already exists, skipping")
|
|
207
|
+
continue
|
|
208
|
+
elif e.json["message"].startswith("Maximum number of custom metrics reached"):
|
|
209
|
+
# Reached the limit - we can't create more
|
|
210
|
+
cleanup_metrics_list = list(self.custom_metric_map.keys())[index:]
|
|
211
|
+
title = "Failed to create custom metric"
|
|
212
|
+
message = (
|
|
213
|
+
f"Metric Name '{metric_name}', "
|
|
214
|
+
"Maximum number of custom metrics reached, "
|
|
215
|
+
f"Cannot create rest of the metrics: {cleanup_metrics_list}"
|
|
216
|
+
)
|
|
217
|
+
self._logger.error(message)
|
|
218
|
+
self.send_event_sync(
|
|
219
|
+
title, message, ModerationEventTypes.MODERATION_METRIC_CREATION_ERROR
|
|
220
|
+
)
|
|
221
|
+
# Lets not raise the exception, for now - break the loop and
|
|
222
|
+
# consolidate valid custom metrics
|
|
223
|
+
break
|
|
224
|
+
# Else raise it and catch in next block
|
|
225
|
+
raise
|
|
226
|
+
except Exception as e:
|
|
227
|
+
title = "Failed to create custom metric"
|
|
228
|
+
message = f"Exception: {e} Custom metric definition: {custom_metric}"
|
|
229
|
+
self._logger.error(title + " " + message)
|
|
230
|
+
self._logger.error(traceback.format_exc())
|
|
231
|
+
cleanup_metrics_list.append(metric_name)
|
|
232
|
+
self.send_event_sync(
|
|
233
|
+
title,
|
|
234
|
+
message,
|
|
235
|
+
ModerationEventTypes.MODERATION_METRIC_CREATION_ERROR,
|
|
236
|
+
metric_name=metric_name,
|
|
237
|
+
)
|
|
238
|
+
# Lets again not raise exception
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
# Now query all the metrics and get their custom metric ids. Specifically,
|
|
242
|
+
# required in case a metric is duplicated, in which case, we don't have its
|
|
243
|
+
# id in the loop above
|
|
244
|
+
#
|
|
245
|
+
# We have to go through pagination - dmm list_custom_metrics does not implement
|
|
246
|
+
# pagination
|
|
247
|
+
custom_metrics_list = []
|
|
248
|
+
offset, limit = 0, 50
|
|
249
|
+
while True:
|
|
250
|
+
response_list = self.dr_client.get(
|
|
251
|
+
f"deployments/{self._deployment_id}/customMetrics/?offset={offset}&limit={limit}"
|
|
252
|
+
).json()
|
|
253
|
+
custom_metrics_list.extend(response_list["data"])
|
|
254
|
+
offset += response_list["count"]
|
|
255
|
+
if response_list["next"] is None:
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
for metric in custom_metrics_list:
|
|
259
|
+
metric_name = metric["name"]
|
|
260
|
+
if metric_name not in self.custom_metric_map:
|
|
261
|
+
self._logger.error(f"Metric '{metric_name}' exists at DR but not in moderation")
|
|
262
|
+
continue
|
|
263
|
+
self.custom_metric_map[metric_name]["id"] = metric["id"]
|
|
264
|
+
self.custom_metric_map[metric_name]["requires_association_id"] = (
|
|
265
|
+
self._requires_association_id(metric_name)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# These are the metrics we couldn't create - so, don't track them
|
|
269
|
+
for metric_name in cleanup_metrics_list:
|
|
270
|
+
if not self.custom_metric_map[metric_name].get("id"):
|
|
271
|
+
self._logger.error(f"Skipping metric creation: {metric_name}")
|
|
272
|
+
del self.custom_metric_map[metric_name]
|
|
273
|
+
|
|
274
|
+
def _requires_association_id(self, metric_name):
|
|
275
|
+
return metric_name not in self.custom_metrics_no_association_ids
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def prediction_url(self):
|
|
279
|
+
return self._datarobot_url
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def api_token(self):
|
|
283
|
+
return self._datarobot_api_token
|
|
284
|
+
|
|
285
|
+
def get_association_id_column_name(self):
|
|
286
|
+
return self._association_id_column_name
|
|
287
|
+
|
|
288
|
+
def get_new_metrics_payload(self):
|
|
289
|
+
"""
|
|
290
|
+
Resets the data for aggregate metrics reporting based on the `custom_metric_map`.
|
|
291
|
+
|
|
292
|
+
It will create the custom-metrics in DR app, if they have been delayed (e.g. originally
|
|
293
|
+
inactive).
|
|
294
|
+
"""
|
|
295
|
+
if self._deployment_id is None:
|
|
296
|
+
return
|
|
297
|
+
if self.delayed_custom_metric_creation:
|
|
298
|
+
# Try creating custom metrics now if possible
|
|
299
|
+
self.create_custom_metrics_if_any()
|
|
300
|
+
if self.delayed_custom_metric_creation:
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
self._query_association_id_column_name()
|
|
304
|
+
|
|
305
|
+
if self._deployment is None:
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
self.aggregate_custom_metric = dict()
|
|
309
|
+
for metric_name, metric_info in self.custom_metric_map.items():
|
|
310
|
+
if not metric_info["requires_association_id"]:
|
|
311
|
+
self.aggregate_custom_metric[metric_name] = {
|
|
312
|
+
"customMetricId": str(metric_info["id"])
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
def set_custom_metrics_aggregate_entry(self, entry, value):
|
|
316
|
+
if isinstance(value, np.generic):
|
|
317
|
+
entry["value"] = value.item()
|
|
318
|
+
else:
|
|
319
|
+
entry["value"] = value
|
|
320
|
+
entry["timestamp"] = str(datetime.now(timezone.utc).isoformat())
|
|
321
|
+
entry["sampleSize"] = 1
|
|
322
|
+
|
|
323
|
+
def upload_custom_metrics(self, payload):
|
|
324
|
+
if len(payload["buckets"]) == 0:
|
|
325
|
+
self._logger.warning("No custom metrics to report, empty payload")
|
|
326
|
+
return
|
|
327
|
+
url = self._datarobot_url + "/" + self._custom_metrics_bulk_upload_url
|
|
328
|
+
asyncio.run(self.async_upload_custom_metrics(url, payload))
|
|
329
|
+
|
|
330
|
+
async def async_upload_custom_metrics(self, url, payload):
|
|
331
|
+
upload_task = self.async_http_client.loop.create_task(
|
|
332
|
+
self.async_http_client.bulk_upload_custom_metrics(url, payload, self._deployment_id)
|
|
333
|
+
)
|
|
334
|
+
self.upload_custom_metrics_tasks.add(upload_task)
|
|
335
|
+
upload_task.add_done_callback(self.upload_custom_metrics_tasks.discard)
|
|
336
|
+
await asyncio.sleep(0)
|
|
337
|
+
|
|
338
|
+
def add_aggregate_metrics_to_payload(self, payload):
|
|
339
|
+
"""
|
|
340
|
+
Takes the provided payload and add aggregate metric values to it.
|
|
341
|
+
Then, uploads the updated payload to the DR app using the bulk upload url.
|
|
342
|
+
"""
|
|
343
|
+
if self._model_id:
|
|
344
|
+
payload["modelId"] = self._model_id
|
|
345
|
+
|
|
346
|
+
for metric_name, metric_value in self.aggregate_custom_metric.items():
|
|
347
|
+
if "value" not in metric_value:
|
|
348
|
+
# Different exception paths - especially with asyncio can
|
|
349
|
+
# end up not adding values for some aggregated custom metrics
|
|
350
|
+
# Capturing them for future fixes
|
|
351
|
+
self._logger.warning(f"No value for custom metric {metric_name}")
|
|
352
|
+
continue
|
|
353
|
+
if not math.isnan(metric_value["value"]):
|
|
354
|
+
payload["buckets"].append(metric_value)
|
|
355
|
+
|
|
356
|
+
self._logger.debug(f"Payload: {payload}")
|
|
357
|
+
return payload
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def custom_metrics(self):
|
|
361
|
+
return {
|
|
362
|
+
metric_name: metric_info for metric_name, metric_info in self.custom_metric_map.items()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
def send_event_sync(self, title, message, event_type, guard_name=None, metric_name=None):
|
|
366
|
+
if self._deployment_id is None:
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
MLOpsEvent.report_moderation_event(
|
|
370
|
+
event_type=event_type,
|
|
371
|
+
title=title,
|
|
372
|
+
message=message,
|
|
373
|
+
deployment_id=self._deployment_id,
|
|
374
|
+
metric_name=metric_name,
|
|
375
|
+
guard_name=guard_name,
|
|
376
|
+
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# ---------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2025 DataRobot, Inc. and its affiliates. All rights reserved.
|
|
3
|
+
# Last updated 2025.
|
|
4
|
+
#
|
|
5
|
+
# DataRobot, Inc. Confidential.
|
|
6
|
+
# This is proprietary source code of DataRobot, Inc. and its affiliates.
|
|
7
|
+
#
|
|
8
|
+
# This file and its contents are subject to DataRobot Tool and Utility Agreement.
|
|
9
|
+
# For details, see
|
|
10
|
+
# https://www.datarobot.com/wp-content/uploads/2021/07/DataRobot-Tool-and-Utility-Agreement.pdf.
|
|
11
|
+
# ---------------------------------------------------------------------------------
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from datarobot.enums import CustomMetricAggregationType
|
|
16
|
+
from datarobot.enums import CustomMetricDirectionality
|
|
17
|
+
|
|
18
|
+
from datarobot_dome.constants import CUSTOM_METRIC_DESCRIPTION_SUFFIX
|
|
19
|
+
from datarobot_dome.constants import LOGGER_NAME_PREFIX
|
|
20
|
+
from datarobot_dome.metrics.factory import MetricScorerFactory
|
|
21
|
+
from datarobot_dome.metrics.metric_scorer import MetricScorer
|
|
22
|
+
from datarobot_dome.metrics.metric_scorer import ScorerType
|
|
23
|
+
from datarobot_dome.pipeline.pipeline import Pipeline
|
|
24
|
+
|
|
25
|
+
LATENCY_NAME = "VDB Score Latency"
|
|
26
|
+
|
|
27
|
+
score_latency = {
|
|
28
|
+
"name": LATENCY_NAME,
|
|
29
|
+
"directionality": CustomMetricDirectionality.LOWER_IS_BETTER,
|
|
30
|
+
"units": "seconds",
|
|
31
|
+
"type": CustomMetricAggregationType.AVERAGE,
|
|
32
|
+
"baselineValue": 0,
|
|
33
|
+
"isModelSpecific": True,
|
|
34
|
+
"timeStep": "hour",
|
|
35
|
+
"description": f"Latency of actual VDB Score. {CUSTOM_METRIC_DESCRIPTION_SUFFIX}",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class VDBPipeline(Pipeline):
|
|
40
|
+
def __init__(self):
|
|
41
|
+
super().__init__()
|
|
42
|
+
self._score_configs: dict[ScorerType, dict[str, Any]] = {
|
|
43
|
+
ScorerType.CITATION_TOKEN_AVERAGE: {},
|
|
44
|
+
ScorerType.CITATION_TOKEN_COUNT: {},
|
|
45
|
+
ScorerType.DOCUMENT_AVERAGE: {},
|
|
46
|
+
ScorerType.DOCUMENT_COUNT: {},
|
|
47
|
+
}
|
|
48
|
+
self._scorers: list[MetricScorer] = list()
|
|
49
|
+
self._logger = logging.getLogger(LOGGER_NAME_PREFIX + "." + self.__class__.__name__)
|
|
50
|
+
self._add_default_custom_metrics()
|
|
51
|
+
self.create_custom_metrics_if_any()
|
|
52
|
+
self.create_scorers()
|
|
53
|
+
|
|
54
|
+
def _add_default_custom_metrics(self):
|
|
55
|
+
"""Adds the default custom metrics based on the `_score_configs` map."""
|
|
56
|
+
# create a list of tuples, so we can track the scorer type
|
|
57
|
+
metric_list = [(score_latency, None)]
|
|
58
|
+
for score_type, score_config in self._score_configs.items():
|
|
59
|
+
metric_config = MetricScorerFactory.custom_metric_config(score_type, score_config)
|
|
60
|
+
metric_list.append((metric_config, score_type))
|
|
61
|
+
|
|
62
|
+
# Metric list so far does not need association id for reporting
|
|
63
|
+
for metric_config, score_type in metric_list:
|
|
64
|
+
name = metric_config["name"]
|
|
65
|
+
self.custom_metrics_no_association_ids.append(name)
|
|
66
|
+
self.custom_metric_map[name] = {
|
|
67
|
+
"metric_definition": metric_config,
|
|
68
|
+
"scorer_type": score_type,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def create_scorers(self):
|
|
72
|
+
"""
|
|
73
|
+
Creates a scorer for each metric in the custom_metric_map list.
|
|
74
|
+
|
|
75
|
+
NOTE: all metrics that failed to be created in DR app have been removed
|
|
76
|
+
"""
|
|
77
|
+
if not self._deployment:
|
|
78
|
+
self._logger.debug("Skipping creation of scorers due to no deployment")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
input_column = self._deployment.model["target_name"]
|
|
82
|
+
for metric_name, metric_data in self.custom_metric_map.items():
|
|
83
|
+
score_type = metric_data.get("scorer_type")
|
|
84
|
+
if not score_type:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
score_config = self._score_configs.get(score_type)
|
|
88
|
+
if score_config.get("input_column") is None:
|
|
89
|
+
score_config["input_column"] = input_column
|
|
90
|
+
scorer = MetricScorerFactory.create(score_type, score_config)
|
|
91
|
+
self._scorers.append(scorer)
|
|
92
|
+
|
|
93
|
+
def scorers(self) -> list[MetricScorer]:
|
|
94
|
+
"""Get all scorers for this pipeline."""
|
|
95
|
+
return self._scorers
|
|
96
|
+
|
|
97
|
+
def record_aggregate_value(self, metric_name: str, value: Any) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Locally records the metric_name/value in the pipeline's area for aggregate metrics where the
|
|
100
|
+
bulk upload with pick it up.
|
|
101
|
+
"""
|
|
102
|
+
if self.aggregate_custom_metric is None:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
entry = self.aggregate_custom_metric[metric_name]
|
|
106
|
+
self.set_custom_metrics_aggregate_entry(entry, value)
|
|
107
|
+
|
|
108
|
+
def record_score_latency(self, latency_in_sec: float):
|
|
109
|
+
"""Records aggregate latency metric value locally"""
|
|
110
|
+
self.record_aggregate_value(LATENCY_NAME, latency_in_sec)
|
|
111
|
+
|
|
112
|
+
def report_custom_metrics(self):
|
|
113
|
+
"""
|
|
114
|
+
Reports all the custom-metrics to DR app.
|
|
115
|
+
|
|
116
|
+
The bulk upload includes grabbing all the aggregated metrics.
|
|
117
|
+
"""
|
|
118
|
+
if self.delayed_custom_metric_creation:
|
|
119
|
+
# Flag is not set yet, so no point reporting custom metrics
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
if not self._deployment:
|
|
123
|
+
# in "test" mode, there is not a deployment and therefore no custom_metrics
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
payload = self.add_aggregate_metrics_to_payload({"buckets": []})
|
|
127
|
+
self.upload_custom_metrics(payload)
|