datarobot-moderations 11.2.4__py3-none-any.whl → 11.2.5__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.
@@ -74,8 +74,8 @@ PROMPT_TOKEN_COUNT_COLUMN_NAME_FROM_USAGE = "prompt_token_count_from_usage"
74
74
  RESPONSE_TOKEN_COUNT_COLUMN_NAME_FROM_USAGE = "response_token_count_from_usage"
75
75
 
76
76
  SPAN_PREFIX = "datarobot.guard"
77
- DATAROBOT_EXTRA_BODY_PREFIX = "datarobot_"
78
77
  DATAROBOT_ASSOCIATION_ID_FIELD_NAME = "datarobot_association_id"
78
+ DATAROBOT_METRICS_DICT_FIELD_NAME = "datarobot_metrics"
79
79
 
80
80
 
81
81
  class TargetType(str, Enum):
@@ -42,7 +42,7 @@ from datarobot_dome.constants import AGENTIC_PIPELINE_INTERACTIONS_ATTR
42
42
  from datarobot_dome.constants import CHAT_COMPLETION_OBJECT
43
43
  from datarobot_dome.constants import CITATIONS_ATTR
44
44
  from datarobot_dome.constants import DATAROBOT_ASSOCIATION_ID_FIELD_NAME
45
- from datarobot_dome.constants import DATAROBOT_EXTRA_BODY_PREFIX
45
+ from datarobot_dome.constants import DATAROBOT_METRICS_DICT_FIELD_NAME
46
46
  from datarobot_dome.constants import DATAROBOT_MODERATIONS_ATTR
47
47
  from datarobot_dome.constants import DISABLE_MODERATION_RUNTIME_PARAM_NAME
48
48
  from datarobot_dome.constants import LLM_BLUEPRINT_ID_ATTR
@@ -721,23 +721,24 @@ def report_otel_evaluation_set_metric(pipeline, result_df):
721
721
 
722
722
  def filter_extra_body(
723
723
  completion_create_params: CompletionCreateParams,
724
- ) -> tuple[CompletionCreateParams, list]:
724
+ ) -> tuple[CompletionCreateParams, dict]:
725
725
  """
726
726
  completion_create_params is a typed dict of a few standard fields,
727
727
  and arbitrary fields from extra_body.
728
- For all fields matching "datarobot_", copy them to a list for later use, and remove them
729
- from completion_create_params.
728
+ If "datarobot_metrics" is in extra_body, process it here.
729
+ Save its value only if it is a dict as expected.
730
730
  :param completion_create_params: the chat completion params from OpenAI client via DRUM
731
- :return: filtered completion_create_params and list of "datarobot_" fields
731
+ :return: filtered completion_create_params; dict of {name: value} for "datarobot_" fields
732
732
  """
733
- datarobot_extra_body_params = []
734
- our_param_names = [
735
- p for p in completion_create_params if p.startswith(DATAROBOT_EXTRA_BODY_PREFIX)
736
- ]
737
- for name in our_param_names:
733
+ datarobot_extra_body_params = {}
734
+ name = DATAROBOT_METRICS_DICT_FIELD_NAME
735
+ if name in completion_create_params:
738
736
  value = completion_create_params[name]
739
- datarobot_extra_body_params.append({name: value})
740
- _logger.debug("found DataRobot parameter in extra_body: %s", f"{name}={value}")
737
+ _logger.debug("found DataRobot metrics in extra_body: %s", f"{name}={value}")
738
+ if isinstance(value, dict):
739
+ datarobot_extra_body_params = copy.deepcopy(value)
740
+ else:
741
+ _logger.warning("DataRobot metrics in extra_body is not a dict: %s", f"{name}={value}")
741
742
  completion_create_params.pop(name, None)
742
743
  return completion_create_params, datarobot_extra_body_params
743
744
 
@@ -770,9 +771,14 @@ def guard_chat_wrapper(
770
771
  # if association ID was included in extra_body, extract field name and value
771
772
  completion_create_params, eb_assoc_id_value = filter_association_id(completion_create_params)
772
773
 
773
- # todo future: filter extra_body params here; pass to pipeline.report_custom_metrics
774
- # completion_create_params, chat_extra_body_params = filter_extra_body(completion_create_params)
774
+ # extract any fields mentioned in "datarobot_metrics" to send as custom metrics later
775
+ completion_create_params, chat_extra_body_params = filter_extra_body(completion_create_params)
776
+
777
+ # register them for ID lookup later
778
+ pipeline.add_extra_body_custom_metric_definition(chat_extra_body_params)
775
779
 
780
+ # define all pipeline-based and guard-based custom metrics (but not those from extra_body)
781
+ # sends single API request to look up IDs for all custom metrics (including extra_body)
776
782
  pipeline.get_new_metrics_payload()
777
783
 
778
784
  # the chat request is not a dataframe, but we'll build a DF internally for moderation.
@@ -795,6 +801,10 @@ def guard_chat_wrapper(
795
801
  if association_id:
796
802
  data[association_id_column_name] = [association_id]
797
803
 
804
+ # report any metrics from extra_body. They are not tied to a prompt or response phase.
805
+ _logger.debug("Report extra_body params as custom metrics")
806
+ pipeline.report_custom_metrics_from_extra_body(association_id, chat_extra_body_params)
807
+
798
808
  # ==================================================================
799
809
  # Step 1: Prescore Guards processing
800
810
  #
@@ -804,11 +814,6 @@ def guard_chat_wrapper(
804
814
  _logger.debug(filtered_df)
805
815
  _logger.debug(f"Pre Score Guard Latency: {prescore_latency} sec")
806
816
 
807
- # todo future: add extra_body parameters to custom metrics reporting
808
- # _logger.debug("Add extra_body params as custom metrics")
809
- # for param in chat_extra_body_params:
810
- # _logger.debug(f"Future: add extra_body param: {param}")
811
-
812
817
  blocked_prompt_column_name = f"blocked_{prompt_column_name}"
813
818
  if prescore_df.loc[0, blocked_prompt_column_name]:
814
819
  pipeline.report_custom_metrics(prescore_df)
@@ -974,6 +979,8 @@ class ModerationPipeline:
974
979
  Base class to simplify interactions with DRUM.
975
980
  This class is not used outside of testing;
976
981
  moderation_pipeline_factory() will select the LLM or VDB subclass instead.
982
+ Also: Pipeline and ModerationPipeline are separate classes (not in samm hierarchy)
983
+ However, LlmModerationPipeline includes LLMPipeline by composition.
977
984
  """
978
985
 
979
986
  def score(self, input_df: pd.DataFrame, model, drum_score_fn, **kwargs):
@@ -1010,7 +1017,10 @@ class LlmModerationPipeline(ModerationPipeline):
1010
1017
  association_id=None,
1011
1018
  **kwargs,
1012
1019
  ):
1013
- """Calls the standard guard chat function."""
1020
+ """
1021
+ Calls the standard guard chat function.
1022
+ See PythonModelAdapter.chat() in DRUM, which calls chat() here.
1023
+ """
1014
1024
  return guard_chat_wrapper(
1015
1025
  completion_create_params,
1016
1026
  model,
@@ -197,17 +197,17 @@ class LLMPipeline(Pipeline):
197
197
 
198
198
  if guard.has_average_score_custom_metric():
199
199
  metric_def = self._get_average_score_metric_definition(guard)
200
- self.add_custom_metric(metric_def, True)
200
+ self.add_custom_metric_definition(metric_def, True)
201
201
 
202
202
  if guard.has_latency_custom_metric():
203
203
  metric_def = guard.get_latency_custom_metric()
204
- self.add_custom_metric(metric_def, False)
204
+ self.add_custom_metric_definition(metric_def, False)
205
205
 
206
206
  if intervention_action:
207
207
  # Enforced metric for all kinds of guards, as long as they have intervention
208
208
  # action defined - even for token count
209
209
  metric_def = guard.get_enforced_custom_metric(guard_stage, intervention_action)
210
- self.add_custom_metric(metric_def, True)
210
+ self.add_custom_metric_definition(metric_def, True)
211
211
 
212
212
  def _add_default_custom_metrics(self):
213
213
  """Default custom metrics"""
@@ -219,14 +219,14 @@ class LLMPipeline(Pipeline):
219
219
  postscore_guard_latency_custom_metric,
220
220
  score_latency,
221
221
  ]:
222
- self.add_custom_metric(metric_def, False)
222
+ self.add_custom_metric_definition(metric_def, False)
223
223
 
224
224
  # These metrics report with an association-id
225
225
  for metric_def in [
226
226
  get_blocked_custom_metric(GuardStage.PROMPT),
227
227
  get_blocked_custom_metric(GuardStage.RESPONSE),
228
228
  ]:
229
- self.add_custom_metric(metric_def, True)
229
+ self.add_custom_metric_definition(metric_def, True)
230
230
 
231
231
  def _add_guard_to_pipeline(self, guard):
232
232
  if guard.stage == GuardStage.PROMPT:
@@ -380,6 +380,55 @@ class LLMPipeline(Pipeline):
380
380
  buckets = self._add_guard_specific_custom_metrics(row, self.get_postscore_guards())
381
381
  payload["buckets"].extend(buckets)
382
382
 
383
+ def add_extra_body_custom_metric_definition(self, chat_extra_body_params: dict) -> None:
384
+ """
385
+ For each name found in extra_body earlier, add to the internal map for reporting later.
386
+ Custom metric IDs are looked up in create_custom_metrics().
387
+ :param chat_extra_body_params: dict of name=value pairs
388
+ """
389
+ for name in chat_extra_body_params.keys():
390
+ self.extra_body_custom_metric_map[name] = {"id": None}
391
+
392
+ def report_custom_metrics_from_extra_body(
393
+ self, association_id: str, extra_params: dict
394
+ ) -> None:
395
+ """
396
+ Add any key-value pairs extracted from extra_body as custom metrics.
397
+ :param association_id: Association ID of the chat request
398
+ :param extra_params: a dict of {"name": value} for all extra_body parameters found
399
+ """
400
+ # If no association ID is defined for deployment, custom metrics will not be processed
401
+ if self._association_id_column_name is None:
402
+ return
403
+ if not extra_params:
404
+ return # nothing to send
405
+ payload = {"buckets": []}
406
+ for name, value in extra_params.items():
407
+ if name in self.custom_metric_map:
408
+ # In case of name collision:
409
+ # the extra_body metric will _not_ override the other moderation metric
410
+ self._logger.warning(
411
+ "extra_body custom metric name is already in use in moderation; "
412
+ f"will not be sent: {name}"
413
+ )
414
+ continue
415
+ if name not in self.extra_body_custom_metric_map:
416
+ self._logger.warning(f"extra_body custom metric ID not in map: {name}")
417
+ continue
418
+ metric_id = self.extra_body_custom_metric_map[name].get("id")
419
+ if not metric_id:
420
+ self._logger.warning(f"extra_body custom metric has missing ID: {name}")
421
+ continue
422
+ payload["buckets"].append(
423
+ {
424
+ "customMetricId": metric_id,
425
+ "value": value,
426
+ "associationId": association_id,
427
+ }
428
+ )
429
+ self._logger.debug(f"Sending custom metrics payload from extra_body: {payload}")
430
+ self.upload_custom_metrics(payload)
431
+
383
432
  def report_custom_metrics(self, result_df):
384
433
  if self.delayed_custom_metric_creation:
385
434
  # Flag is not set yet, so no point reporting custom metrics
@@ -41,7 +41,6 @@ class Pipeline:
41
41
 
42
42
  def __init__(self, async_http_timeout_sec=DEFAULT_GUARD_PREDICTION_TIMEOUT_IN_SEC):
43
43
  self._logger = logging.getLogger(LOGGER_NAME_PREFIX + "." + self.__class__.__name__)
44
- self.custom_metric = {}
45
44
  self._deployment = None
46
45
  self._association_id_column_name = None
47
46
  self._datarobot_url = None
@@ -54,6 +53,8 @@ class Pipeline:
54
53
  self._custom_metrics_bulk_upload_url = None
55
54
  self.aggregate_custom_metric = None
56
55
  self.custom_metric_map = dict()
56
+ # even though only LLMPipeline really uses the next map, Pipeline still references it
57
+ self.extra_body_custom_metric_map = dict()
57
58
  self.delayed_custom_metric_creation = False
58
59
  self.upload_custom_metrics_tasks = set()
59
60
 
@@ -77,6 +78,13 @@ class Pipeline:
77
78
  self._logger.warning(f"Missing DataRobot API Token, {self.common_message}")
78
79
  return
79
80
 
81
+ self._deployment_id = os.environ.get("MLOPS_DEPLOYMENT_ID", None)
82
+ if self._deployment_id is None:
83
+ self._logger.warning(
84
+ f"DataRobot deployment id not exported (MLOPS_DEPLOYMENT_ID), {self.common_message}"
85
+ )
86
+ return
87
+
80
88
  # This is regular / default DataRobot Client
81
89
  self.dr_client = dr.Client(endpoint=self._datarobot_url, token=self._datarobot_api_token)
82
90
  self._headers = {
@@ -91,7 +99,6 @@ class Pipeline:
91
99
  moderations for
92
100
  :return:
93
101
  """
94
- self._deployment_id = os.environ.get("MLOPS_DEPLOYMENT_ID", None)
95
102
  if self._deployment_id is None:
96
103
  self._logger.warning(f'Custom Model workshop "test" mode?, {self.common_message}')
97
104
  return
@@ -172,13 +179,17 @@ class Pipeline:
172
179
  self.create_custom_metrics()
173
180
  self.delayed_custom_metric_creation = False
174
181
 
175
- def add_custom_metric(
182
+ def add_custom_metric_definition(
176
183
  self, metric_definition: dict[str, Any], requires_association_id: bool, **kwargs
177
184
  ) -> None:
178
185
  """
179
186
  Adds an entry to the `custom_metric_map`.
187
+ Only 2 functions should write to this map:
188
+ * this function -- links the custom metric definition to its name
189
+ * create_custom_metrics() -- queries DR for the object ID and links it to the name
180
190
 
181
191
  NOTE: the kwargs allow implementations to add their own specialized values.
192
+ Currently only VDBPipeline calls this with kwargs.
182
193
  """
183
194
  name = metric_definition["name"]
184
195
  self.custom_metric_map[name] = {
@@ -193,6 +204,8 @@ class Pipeline:
193
204
 
194
205
  Updates the `custom_metric_map` with id's to insure the appropriate data
195
206
  is put in place for reporting.
207
+
208
+ Every custom metric we want to use must already exist by name in the map.
196
209
  """
197
210
  cleanup_metrics_list = list()
198
211
  for index, (metric_name, custom_metric) in enumerate(self.custom_metric_map.items()):
@@ -256,6 +269,10 @@ class Pipeline:
256
269
  # required in case a metric is duplicated, in which case, we don't have its
257
270
  # id in the loop above
258
271
  #
272
+ # Note: user-defined custom metrics (such as for extra_body params) will also
273
+ # be included in the list. We will warn if an existing metric is not found
274
+ # in either list, which means this chat request does not have a value for it.
275
+ #
259
276
  # We have to go through pagination - dmm list_custom_metrics does not implement
260
277
  # pagination
261
278
  custom_metrics_list = []
@@ -269,12 +286,19 @@ class Pipeline:
269
286
  if response_list["next"] is None:
270
287
  break
271
288
 
289
+ # assign IDs to the "metric by name" maps so we can upload by ID later
272
290
  for metric in custom_metrics_list:
273
291
  metric_name = metric["name"]
274
- if metric_name not in self.custom_metric_map:
275
- self._logger.error(f"Metric '{metric_name}' exists at DR but not in moderation")
292
+ if metric_name in self.custom_metric_map:
293
+ self.custom_metric_map[metric_name]["id"] = metric["id"]
294
+ elif metric_name in self.extra_body_custom_metric_map:
295
+ self.extra_body_custom_metric_map[metric_name]["id"] = metric["id"]
296
+ else:
297
+ self._logger.warning(
298
+ f"Metric '{metric_name}' exists at DR but not in moderation; "
299
+ "no value will be reported"
300
+ )
276
301
  continue
277
- self.custom_metric_map[metric_name]["id"] = metric["id"]
278
302
 
279
303
  # These are the metrics we couldn't create - so, don't track them
280
304
  for metric_name in cleanup_metrics_list:
@@ -70,7 +70,7 @@ class VDBPipeline(Pipeline):
70
70
 
71
71
  # Metric list so far does not need association id for reporting
72
72
  for metric_def, per_row, score_type in metric_list:
73
- self.add_custom_metric(metric_def, per_row, scorer_type=score_type)
73
+ self.add_custom_metric_definition(metric_def, per_row, scorer_type=score_type)
74
74
 
75
75
  def create_scorers(self):
76
76
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-moderations
3
- Version: 11.2.4
3
+ Version: 11.2.5
4
4
  Summary: DataRobot Monitoring and Moderation framework
5
5
  License: DataRobot Tool and Utility Agreement
6
6
  Author: DataRobot
@@ -19,7 +19,7 @@ Requires-Dist: deepeval (>=3.3.5)
19
19
  Requires-Dist: langchain (>=0.1.12)
20
20
  Requires-Dist: langchain-nvidia-ai-endpoints (>=0.3.9)
21
21
  Requires-Dist: langchain-openai (>=0.1.7)
22
- Requires-Dist: llama-index (>=0.12.49)
22
+ Requires-Dist: llama-index (>=0.13.0)
23
23
  Requires-Dist: llama-index-embeddings-azure-openai (>=0.1.6)
24
24
  Requires-Dist: llama-index-llms-bedrock-converse (>=0.1.6)
25
25
  Requires-Dist: llama-index-llms-langchain (>=0.1.3)
@@ -1,8 +1,8 @@
1
1
  datarobot_dome/__init__.py,sha256=B5Rx8_CNCNsOpxBbRj27XOXCfRZmvmrAR-NzlzIKnDw,583
2
2
  datarobot_dome/async_http_client.py,sha256=cQFoSI2ovt0Kyk4XWQPXod5PAfA-ZPkjLYVWQZhDGDE,9809
3
3
  datarobot_dome/chat_helper.py,sha256=BzvtUyZSZxzOqq-5a2wQKhHhr2kMlcP1MFrHaDAeD_o,9671
4
- datarobot_dome/constants.py,sha256=EtdmYdEp9H2awbJVo2Xfmk5PFCJ0nymMSAPIAt8pQgE,10649
5
- datarobot_dome/drum_integration.py,sha256=nLENtjQEP4nwwyrtesQTj2844I-ap_HwHKvijfxz0Ng,45121
4
+ datarobot_dome/constants.py,sha256=jvgpHa3Wh_nZVZmfU-6ab8FHnKNW3KxOPYIIEb_oS6U,10662
5
+ datarobot_dome/drum_integration.py,sha256=DIGeiw6fwKCbgi6mb-mJdjPEB6C1uMc-pZotpTnlGZQ,45770
6
6
  datarobot_dome/guard.py,sha256=xJds9hcbUaS-KD5nC1mn0GiPdBrileFUu6BuTAjDNuY,34668
7
7
  datarobot_dome/guard_executor.py,sha256=ox5_jOHcqMaxaaagIYJJHhCwEI7Wg-rUEiu5rutsfVU,35363
8
8
  datarobot_dome/guard_helpers.py,sha256=jfu8JTWCcxu4WD1MKxeP1n53DeebY3SSuP-t5sWyV1U,17187
@@ -14,11 +14,11 @@ datarobot_dome/metrics/citation_metrics.py,sha256=l2mnV1gz7nQeJ_yfaS4dcP3DFWf0p5
14
14
  datarobot_dome/metrics/factory.py,sha256=7caa8paI9LuFXDgguXdC4on28V7IwwIsKJT2Z-Aps8A,2187
15
15
  datarobot_dome/metrics/metric_scorer.py,sha256=uJ_IJRw7ZFHueg8xjsaXbt0ypO7JiydZ0WapCp96yng,2540
16
16
  datarobot_dome/pipeline/__init__.py,sha256=B5Rx8_CNCNsOpxBbRj27XOXCfRZmvmrAR-NzlzIKnDw,583
17
- datarobot_dome/pipeline/llm_pipeline.py,sha256=4Q-DW8lzKdPBDTNgO-wI-Pyl53IRZNJcjJpfE3kiv08,18813
18
- datarobot_dome/pipeline/pipeline.py,sha256=GM1mmFtk4xm2xmHiFOefno4K38FNjdMfrynpsp6MLX0,17511
19
- datarobot_dome/pipeline/vdb_pipeline.py,sha256=q3c_Z-hGUqhH6j6n8VpS3wZiBIkWgpRDsBnyJyZhiw4,9855
17
+ datarobot_dome/pipeline/llm_pipeline.py,sha256=g8XZQjJPPs43GPqaBG4TtsCbvu1o7TWraOmzhtUGB_o,21172
18
+ datarobot_dome/pipeline/pipeline.py,sha256=iDR6h25RDp4bnTW1eGWErG9W_ZXDdJ_sQGVD-tT_Ll0,18805
19
+ datarobot_dome/pipeline/vdb_pipeline.py,sha256=zt5d_41oJjdT8qOtvpgz-l5uvImwKE9f6pQsAU_TdR4,9866
20
20
  datarobot_dome/runtime.py,sha256=FD8wXOweqoQVzbZMh-mucL66xT2kGxPsJUGAcJBgwxw,1468
21
21
  datarobot_dome/streaming.py,sha256=DkvKEH0yN0aPEWMTAjMFJB3Kx4iLGdjUMQU1pAplbeg,17751
22
- datarobot_moderations-11.2.4.dist-info/METADATA,sha256=iksvFgFDIQZA7DF0vR6fICFawRl7xcdl0hy_E4QAakg,4742
23
- datarobot_moderations-11.2.4.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
24
- datarobot_moderations-11.2.4.dist-info/RECORD,,
22
+ datarobot_moderations-11.2.5.dist-info/METADATA,sha256=9Aj3fZnLvcgKpiAQwnvkkAzA3SxaBq2j45ctWEMCEaI,4741
23
+ datarobot_moderations-11.2.5.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
24
+ datarobot_moderations-11.2.5.dist-info/RECORD,,