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,11 @@
|
|
|
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
|
+
# ---------------------------------------------------------------------------------
|
|
@@ -0,0 +1,248 @@
|
|
|
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 atexit
|
|
14
|
+
import datetime
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
import traceback
|
|
18
|
+
from http import HTTPStatus
|
|
19
|
+
from io import StringIO
|
|
20
|
+
|
|
21
|
+
import aiohttp
|
|
22
|
+
import backoff
|
|
23
|
+
import nest_asyncio
|
|
24
|
+
import pandas as pd
|
|
25
|
+
|
|
26
|
+
from datarobot_dome.constants import DATAROBOT_ACTUAL_ON_PREM_ST_SAAS_URL
|
|
27
|
+
from datarobot_dome.constants import DATAROBOT_CONFIGURED_ON_PREM_ST_SAAS_URL
|
|
28
|
+
from datarobot_dome.constants import DATAROBOT_SERVERLESS_PLATFORM
|
|
29
|
+
from datarobot_dome.constants import DEFAULT_GUARD_PREDICTION_TIMEOUT_IN_SEC
|
|
30
|
+
from datarobot_dome.constants import LOGGER_NAME_PREFIX
|
|
31
|
+
from datarobot_dome.constants import RETRY_COUNT
|
|
32
|
+
from datarobot_dome.constants import ModerationEventTypes
|
|
33
|
+
|
|
34
|
+
RETRY_STATUS_CODES = [
|
|
35
|
+
HTTPStatus.REQUEST_ENTITY_TOO_LARGE,
|
|
36
|
+
HTTPStatus.BAD_GATEWAY,
|
|
37
|
+
HTTPStatus.GATEWAY_TIMEOUT,
|
|
38
|
+
]
|
|
39
|
+
RETRY_AFTER_STATUS_CODES = [HTTPStatus.TOO_MANY_REQUESTS, HTTPStatus.SERVICE_UNAVAILABLE]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# We want this logger to be available for backoff too, hence defining outside the class
|
|
43
|
+
logger = logging.getLogger(LOGGER_NAME_PREFIX + ".AsyncHTTPClient")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Event handlers for backoff
|
|
47
|
+
def _timeout_backoff_handler(details):
|
|
48
|
+
logger.warning(
|
|
49
|
+
f"HTTP Timeout: Backing off {details['wait']} seconds after {details['tries']} tries"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _timeout_giveup_handler(details):
|
|
54
|
+
url = details["args"][1]
|
|
55
|
+
logger.error(f"Giving up predicting on {url}, Retried {details['tries']} after HTTP Timeout")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _retry_backoff_handler(details):
|
|
59
|
+
status_code = details["value"].status
|
|
60
|
+
message = details["value"].reason
|
|
61
|
+
retry_after_value = details["value"].headers.get("Retry-After")
|
|
62
|
+
logger.warning(
|
|
63
|
+
f"Received status code {status_code}, message {message},"
|
|
64
|
+
f" Retry-After val: {retry_after_value} "
|
|
65
|
+
f"Backing off {details['wait']} seconds after {details['tries']} tries"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _retry_giveup_handler(details):
|
|
70
|
+
message = (
|
|
71
|
+
f"Giving up predicting on {details['args'][1]}, Retried {details['tries']} retries, "
|
|
72
|
+
f"elapsed time {details['elapsed']} sec, but couldn't get predictions"
|
|
73
|
+
)
|
|
74
|
+
raise Exception(message)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AsyncHTTPClient:
|
|
78
|
+
def __init__(self, timeout=DEFAULT_GUARD_PREDICTION_TIMEOUT_IN_SEC):
|
|
79
|
+
self._logger = logging.getLogger(LOGGER_NAME_PREFIX + "." + self.__class__.__name__)
|
|
80
|
+
self.csv_headers = {
|
|
81
|
+
"Content-Type": "text/csv",
|
|
82
|
+
"Accept": "text/csv",
|
|
83
|
+
"Authorization": f"Bearer {os.environ['DATAROBOT_API_TOKEN']}",
|
|
84
|
+
}
|
|
85
|
+
self.json_headers = {
|
|
86
|
+
"Content-Type": "application/json",
|
|
87
|
+
"Accept": "application/json",
|
|
88
|
+
"Authorization": f"Bearer {os.environ['DATAROBOT_API_TOKEN']}",
|
|
89
|
+
}
|
|
90
|
+
self.session = None
|
|
91
|
+
self.events_url = f"{os.environ['DATAROBOT_ENDPOINT']}/remoteEvents/"
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
self.loop = asyncio.get_event_loop()
|
|
95
|
+
except RuntimeError as e:
|
|
96
|
+
if str(e).startswith("There is no current event loop in thread") or str(e).startswith(
|
|
97
|
+
"Event loop is closed"
|
|
98
|
+
):
|
|
99
|
+
self.loop = asyncio.new_event_loop()
|
|
100
|
+
asyncio.set_event_loop(self.loop)
|
|
101
|
+
else:
|
|
102
|
+
raise
|
|
103
|
+
self.loop.run_until_complete(self.__create_client_session(timeout))
|
|
104
|
+
self.loop.set_debug(True)
|
|
105
|
+
nest_asyncio.apply(loop=self.loop)
|
|
106
|
+
|
|
107
|
+
atexit.register(self.shutdown)
|
|
108
|
+
|
|
109
|
+
async def __create_client_session(self, timeout):
|
|
110
|
+
client_timeout = aiohttp.ClientTimeout(
|
|
111
|
+
connect=timeout, sock_connect=timeout, sock_read=timeout
|
|
112
|
+
)
|
|
113
|
+
# Creation of client session needs to happen within in async function
|
|
114
|
+
self.session = aiohttp.ClientSession(timeout=client_timeout)
|
|
115
|
+
|
|
116
|
+
def shutdown(self):
|
|
117
|
+
asyncio.run(self.session.close())
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def is_serverless_deployment(deployment):
|
|
121
|
+
if not deployment.prediction_environment:
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
if deployment.prediction_environment.get("platform") == DATAROBOT_SERVERLESS_PLATFORM:
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def is_on_prem_st_saas_endpoint():
|
|
131
|
+
return os.environ.get("DATAROBOT_ENDPOINT").startswith(
|
|
132
|
+
DATAROBOT_CONFIGURED_ON_PREM_ST_SAAS_URL
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@backoff.on_predicate(
|
|
136
|
+
backoff.runtime,
|
|
137
|
+
predicate=lambda r: r.status in RETRY_AFTER_STATUS_CODES,
|
|
138
|
+
value=lambda r: int(r.headers.get("Retry-After", DEFAULT_GUARD_PREDICTION_TIMEOUT_IN_SEC)),
|
|
139
|
+
max_time=RETRY_COUNT * DEFAULT_GUARD_PREDICTION_TIMEOUT_IN_SEC,
|
|
140
|
+
max_tries=RETRY_COUNT,
|
|
141
|
+
jitter=None,
|
|
142
|
+
logger=logger,
|
|
143
|
+
on_backoff=_retry_backoff_handler,
|
|
144
|
+
on_giveup=_retry_giveup_handler,
|
|
145
|
+
)
|
|
146
|
+
@backoff.on_predicate(
|
|
147
|
+
backoff.fibo,
|
|
148
|
+
predicate=lambda r: r.status in RETRY_STATUS_CODES,
|
|
149
|
+
jitter=None,
|
|
150
|
+
max_tries=RETRY_COUNT,
|
|
151
|
+
max_time=RETRY_COUNT * DEFAULT_GUARD_PREDICTION_TIMEOUT_IN_SEC,
|
|
152
|
+
logger=logger,
|
|
153
|
+
on_backoff=_retry_backoff_handler,
|
|
154
|
+
on_giveup=_retry_giveup_handler,
|
|
155
|
+
)
|
|
156
|
+
@backoff.on_exception(
|
|
157
|
+
backoff.fibo,
|
|
158
|
+
asyncio.TimeoutError,
|
|
159
|
+
max_tries=RETRY_COUNT,
|
|
160
|
+
max_time=RETRY_COUNT * DEFAULT_GUARD_PREDICTION_TIMEOUT_IN_SEC,
|
|
161
|
+
logger=logger,
|
|
162
|
+
on_backoff=_timeout_backoff_handler,
|
|
163
|
+
on_giveup=_timeout_giveup_handler,
|
|
164
|
+
raise_on_giveup=True,
|
|
165
|
+
)
|
|
166
|
+
async def post_predict_request(self, url_path, input_df):
|
|
167
|
+
return await self.session.post(
|
|
168
|
+
url_path, data=input_df.to_csv(index=False), headers=self.csv_headers
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
async def predict(self, deployment, input_df):
|
|
172
|
+
deployment_id = str(deployment.id)
|
|
173
|
+
if self.is_on_prem_st_saas_endpoint():
|
|
174
|
+
url_path = DATAROBOT_ACTUAL_ON_PREM_ST_SAAS_URL
|
|
175
|
+
elif self.is_serverless_deployment(deployment):
|
|
176
|
+
url_path = f"{os.environ['DATAROBOT_ENDPOINT']}"
|
|
177
|
+
else:
|
|
178
|
+
prediction_server = deployment.default_prediction_server
|
|
179
|
+
if not prediction_server:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"Can't make prediction request because Deployment object doesn't contain "
|
|
182
|
+
"default prediction server"
|
|
183
|
+
)
|
|
184
|
+
datarobot_key = prediction_server.get("datarobot-key")
|
|
185
|
+
if datarobot_key:
|
|
186
|
+
self.csv_headers["datarobot-key"] = datarobot_key
|
|
187
|
+
|
|
188
|
+
url_path = f"{prediction_server['url']}/predApi/v1.0"
|
|
189
|
+
|
|
190
|
+
url_path += f"/deployments/{deployment_id}/predictions"
|
|
191
|
+
response = await self.post_predict_request(url_path, input_df)
|
|
192
|
+
if not response.ok:
|
|
193
|
+
raise Exception(
|
|
194
|
+
f"Failed to get guard predictions: {response.reason} Status: {response.status}"
|
|
195
|
+
)
|
|
196
|
+
csv_data = await response.text()
|
|
197
|
+
return pd.read_csv(StringIO(csv_data))
|
|
198
|
+
|
|
199
|
+
async def async_report_event(
|
|
200
|
+
self, title, message, event_type, deployment_id, guard_name=None, metric_name=None
|
|
201
|
+
):
|
|
202
|
+
payload = {
|
|
203
|
+
"title": title,
|
|
204
|
+
"message": message,
|
|
205
|
+
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
206
|
+
"deploymentId": str(deployment_id),
|
|
207
|
+
"eventType": event_type,
|
|
208
|
+
"moderationData": {"guardName": "", "metricName": ""},
|
|
209
|
+
}
|
|
210
|
+
error_text = ""
|
|
211
|
+
if metric_name:
|
|
212
|
+
payload["moderationData"]["metricName"] = metric_name
|
|
213
|
+
error_text = f"for metric {metric_name}"
|
|
214
|
+
if guard_name:
|
|
215
|
+
payload["moderationData"]["guardName"] = guard_name
|
|
216
|
+
error_text = f"for guard {guard_name}"
|
|
217
|
+
|
|
218
|
+
response = await self.session.post(self.events_url, json=payload, headers=self.json_headers)
|
|
219
|
+
if response.status != HTTPStatus.CREATED:
|
|
220
|
+
# Lets not raise - we just failed to report an event, let the moderation
|
|
221
|
+
# continue
|
|
222
|
+
logger.error(
|
|
223
|
+
f"Failed to post event {event_type} {error_text} "
|
|
224
|
+
f" Status: {response.status} Message: {response.reason}"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
async def bulk_upload_custom_metrics(self, url, payload, deployment_id):
|
|
228
|
+
self._logger.debug("Uploading custom metrics")
|
|
229
|
+
try:
|
|
230
|
+
response = await self.session.post(url, json=payload, headers=self.json_headers)
|
|
231
|
+
if response.status != HTTPStatus.ACCEPTED:
|
|
232
|
+
raise Exception(
|
|
233
|
+
f"Error uploading custom metrics: Status Code: {response.status_code}"
|
|
234
|
+
f"Message: {response.text}"
|
|
235
|
+
)
|
|
236
|
+
self._logger.info("Successfully uploaded custom metrics")
|
|
237
|
+
except Exception as e:
|
|
238
|
+
title = "Failed to upload custom metrics"
|
|
239
|
+
message = f"Exception: {e} Payload: {payload}"
|
|
240
|
+
self._logger.error(title + " " + message)
|
|
241
|
+
self._logger.error(traceback.format_exc())
|
|
242
|
+
await self.async_report_event(
|
|
243
|
+
title,
|
|
244
|
+
message,
|
|
245
|
+
ModerationEventTypes.MODERATION_METRIC_REPORTING_ERROR,
|
|
246
|
+
deployment_id,
|
|
247
|
+
)
|
|
248
|
+
# Lets not raise the exception, just walk off
|
|
@@ -0,0 +1,227 @@
|
|
|
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
|
+
import time
|
|
14
|
+
import traceback
|
|
15
|
+
from re import match
|
|
16
|
+
|
|
17
|
+
import tiktoken
|
|
18
|
+
|
|
19
|
+
from datarobot_dome.constants import AGENTIC_PIPELINE_INTERACTIONS_ATTR
|
|
20
|
+
from datarobot_dome.constants import NONE_CUSTOM_PY_RESPONSE
|
|
21
|
+
from datarobot_dome.constants import PROMPT_TOKEN_COUNT_COLUMN_NAME_FROM_USAGE
|
|
22
|
+
from datarobot_dome.constants import RESPONSE_TOKEN_COUNT_COLUMN_NAME_FROM_USAGE
|
|
23
|
+
from datarobot_dome.constants import GuardStage
|
|
24
|
+
from datarobot_dome.guard_executor import AsyncGuardExecutor
|
|
25
|
+
from datarobot_dome.guard_helpers import calculate_token_counts_for_cost_calculations
|
|
26
|
+
from datarobot_dome.guard_helpers import get_citation_columns
|
|
27
|
+
from datarobot_dome.guard_helpers import get_rouge_1_score
|
|
28
|
+
|
|
29
|
+
_logger = logging.getLogger("chat_helper")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_all_citation_columns(df):
|
|
33
|
+
citation_columns = []
|
|
34
|
+
for pattern in [
|
|
35
|
+
"CITATION_CONTENT_",
|
|
36
|
+
"CITATION_SOURCE_",
|
|
37
|
+
"CITATION_PAGE_",
|
|
38
|
+
"CITATION_CHUNK_ID_",
|
|
39
|
+
"CITATION_START_INDEX_",
|
|
40
|
+
"CITATION_SIMILARITY_SCORE_",
|
|
41
|
+
]:
|
|
42
|
+
citation_columns.extend(list(filter(lambda column: match(pattern, column), df.columns)))
|
|
43
|
+
return citation_columns
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build_moderations_attribute_for_completion(pipeline, df):
|
|
47
|
+
"""
|
|
48
|
+
Given the dataframe build a moderation attribute to be returned with
|
|
49
|
+
chat completion or chat completion chunk
|
|
50
|
+
"""
|
|
51
|
+
if df is None or df.empty:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
prompt_column_name = pipeline.get_input_column(GuardStage.PROMPT)
|
|
55
|
+
blocked_message_prompt_column_name = f"blocked_message_{prompt_column_name}"
|
|
56
|
+
response_column_name = pipeline.get_input_column(GuardStage.RESPONSE)
|
|
57
|
+
replaced_message_prompt_column_name = f"replaced_message_{prompt_column_name}"
|
|
58
|
+
blocked_message_completion_column_name = f"blocked_message_{response_column_name}"
|
|
59
|
+
replaced_message_response_column_name = f"replaced_message_{response_column_name}"
|
|
60
|
+
|
|
61
|
+
moderations = df.to_dict(orient="records")[0]
|
|
62
|
+
columns_to_drop = [
|
|
63
|
+
pipeline.get_input_column(GuardStage.PROMPT),
|
|
64
|
+
# Its already copied as part of the completion.choices[0].message.content
|
|
65
|
+
pipeline.get_input_column(GuardStage.RESPONSE),
|
|
66
|
+
blocked_message_prompt_column_name,
|
|
67
|
+
blocked_message_completion_column_name,
|
|
68
|
+
replaced_message_prompt_column_name,
|
|
69
|
+
replaced_message_response_column_name,
|
|
70
|
+
f"Noneed_{prompt_column_name}",
|
|
71
|
+
f"Noneed_{response_column_name}",
|
|
72
|
+
]
|
|
73
|
+
citation_columns = get_all_citation_columns(df)
|
|
74
|
+
if len(citation_columns) > 0:
|
|
75
|
+
columns_to_drop += citation_columns
|
|
76
|
+
for column in columns_to_drop:
|
|
77
|
+
if column in moderations:
|
|
78
|
+
moderations.pop(column)
|
|
79
|
+
|
|
80
|
+
return moderations
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def run_postscore_guards(pipeline, predictions_df, postscore_guards=None):
|
|
84
|
+
"""Run postscore guards on the input data."""
|
|
85
|
+
if not postscore_guards:
|
|
86
|
+
postscore_guards = pipeline.get_postscore_guards()
|
|
87
|
+
response_column_name = pipeline.get_input_column(GuardStage.RESPONSE)
|
|
88
|
+
blocked_completion_column_name = f"blocked_{response_column_name}"
|
|
89
|
+
input_df = predictions_df.copy(deep=True)
|
|
90
|
+
if len(postscore_guards) == 0:
|
|
91
|
+
input_df[blocked_completion_column_name] = False
|
|
92
|
+
return input_df, 0
|
|
93
|
+
|
|
94
|
+
start_time = time.time()
|
|
95
|
+
try:
|
|
96
|
+
postscore_df, postscore_latency = AsyncGuardExecutor(pipeline).run_guards(
|
|
97
|
+
input_df, postscore_guards, GuardStage.RESPONSE
|
|
98
|
+
)
|
|
99
|
+
except Exception as ex:
|
|
100
|
+
end_time = time.time()
|
|
101
|
+
_logger.error(f"Failed to run postscore guards: {ex}")
|
|
102
|
+
_logger.error(traceback.format_exc())
|
|
103
|
+
postscore_df = input_df
|
|
104
|
+
postscore_df[blocked_completion_column_name] = False
|
|
105
|
+
postscore_latency = end_time - start_time
|
|
106
|
+
|
|
107
|
+
# Again ensure the indexing matches the input dataframe indexing
|
|
108
|
+
postscore_df.index = predictions_df.index
|
|
109
|
+
_logger.debug("After passing completions through post score guards")
|
|
110
|
+
_logger.debug(postscore_df)
|
|
111
|
+
_logger.debug(f"Post Score Guard Latency: {postscore_latency} sec")
|
|
112
|
+
|
|
113
|
+
return postscore_df, postscore_latency
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_response_message_and_finish_reason(pipeline, postscore_df, streaming=False):
|
|
117
|
+
response_column_name = pipeline.get_input_column(GuardStage.RESPONSE)
|
|
118
|
+
blocked_completion_column_name = f"blocked_{response_column_name}"
|
|
119
|
+
replaced_response_column_name = f"replaced_{response_column_name}"
|
|
120
|
+
if postscore_df.empty:
|
|
121
|
+
response_message = NONE_CUSTOM_PY_RESPONSE
|
|
122
|
+
finish_reason = "stop"
|
|
123
|
+
elif postscore_df.loc[0, blocked_completion_column_name]:
|
|
124
|
+
blocked_message_completion_column_name = f"blocked_message_{response_column_name}"
|
|
125
|
+
response_message = postscore_df.loc[0, blocked_message_completion_column_name]
|
|
126
|
+
finish_reason = "content_filter"
|
|
127
|
+
elif (
|
|
128
|
+
replaced_response_column_name in postscore_df.columns
|
|
129
|
+
and postscore_df.loc[0, replaced_response_column_name]
|
|
130
|
+
):
|
|
131
|
+
replaced_message_response_column_name = f"replaced_message_{response_column_name}"
|
|
132
|
+
response_message = postscore_df.loc[0, replaced_message_response_column_name]
|
|
133
|
+
# In case of streaming - if the guard replaces the text, we don't want to
|
|
134
|
+
# stop streaming - so don't put finish_reason in case of streaming
|
|
135
|
+
finish_reason = None if streaming else "content_filter"
|
|
136
|
+
else:
|
|
137
|
+
response_message = postscore_df.loc[0, response_column_name]
|
|
138
|
+
finish_reason = None if streaming else "stop"
|
|
139
|
+
|
|
140
|
+
return response_message, finish_reason
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def calculate_token_counts_and_confidence_score(pipeline, result_df):
|
|
144
|
+
prompt_column_name = pipeline.get_input_column(GuardStage.PROMPT)
|
|
145
|
+
blocked_prompt_column_name = f"blocked_{prompt_column_name}"
|
|
146
|
+
response_column_name = pipeline.get_input_column(GuardStage.RESPONSE)
|
|
147
|
+
blocked_completion_column_name = f"blocked_{response_column_name}"
|
|
148
|
+
|
|
149
|
+
encoding = tiktoken.get_encoding("cl100k_base")
|
|
150
|
+
|
|
151
|
+
citation_columns = get_citation_columns(result_df.columns)
|
|
152
|
+
|
|
153
|
+
def _get_llm_contexts(index):
|
|
154
|
+
contexts = []
|
|
155
|
+
if len(citation_columns) >= 0:
|
|
156
|
+
for column in citation_columns:
|
|
157
|
+
contexts.append(result_df.loc[index][column])
|
|
158
|
+
return contexts
|
|
159
|
+
|
|
160
|
+
for index, row in result_df.iterrows():
|
|
161
|
+
if not (
|
|
162
|
+
row.get(blocked_prompt_column_name, False)
|
|
163
|
+
or row.get(blocked_completion_column_name, False)
|
|
164
|
+
):
|
|
165
|
+
completion = result_df.loc[index][response_column_name]
|
|
166
|
+
if completion != NONE_CUSTOM_PY_RESPONSE:
|
|
167
|
+
result_df.loc[index, "datarobot_token_count"] = len(
|
|
168
|
+
encoding.encode(str(completion), disallowed_special=())
|
|
169
|
+
)
|
|
170
|
+
result_df.loc[index, "datarobot_confidence_score"] = get_rouge_1_score(
|
|
171
|
+
pipeline.rouge_scorer, _get_llm_contexts(index), [completion]
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
result_df.loc[index, "datarobot_confidence_score"] = 0.0
|
|
175
|
+
else:
|
|
176
|
+
# If the row is blocked, set default value
|
|
177
|
+
result_df.loc[index, "datarobot_confidence_score"] = 0.0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def add_citations_to_df(citations, df):
|
|
181
|
+
if not citations:
|
|
182
|
+
return df
|
|
183
|
+
|
|
184
|
+
for index, citation in enumerate(citations):
|
|
185
|
+
df[f"CITATION_CONTENT_{index}"] = citation["content"]
|
|
186
|
+
return df
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def add_token_count_columns_to_df(pipeline, df, usage=None):
|
|
190
|
+
if not usage:
|
|
191
|
+
prompt_column_name = pipeline.get_input_column(GuardStage.PROMPT)
|
|
192
|
+
response_column_name = pipeline.get_input_column(GuardStage.RESPONSE)
|
|
193
|
+
df = calculate_token_counts_for_cost_calculations(
|
|
194
|
+
prompt_column_name, response_column_name, df
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
df[PROMPT_TOKEN_COUNT_COLUMN_NAME_FROM_USAGE] = [usage.prompt_tokens]
|
|
198
|
+
df[RESPONSE_TOKEN_COUNT_COLUMN_NAME_FROM_USAGE] = [usage.completion_tokens]
|
|
199
|
+
return df
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def remove_unnecessary_columns(pipeline, result_df):
|
|
203
|
+
prompt_column_name = pipeline.get_input_column(GuardStage.PROMPT)
|
|
204
|
+
blocked_message_prompt_column_name = f"blocked_message_{prompt_column_name}"
|
|
205
|
+
response_column_name = pipeline.get_input_column(GuardStage.RESPONSE)
|
|
206
|
+
replaced_message_prompt_column_name = f"replaced_message_{prompt_column_name}"
|
|
207
|
+
blocked_message_completion_column_name = f"blocked_message_{response_column_name}"
|
|
208
|
+
replaced_message_response_column_name = f"replaced_message_{response_column_name}"
|
|
209
|
+
# We don't need these columns, because they have already been copied into
|
|
210
|
+
# 'completion' column
|
|
211
|
+
columns_to_remove = [
|
|
212
|
+
blocked_message_prompt_column_name,
|
|
213
|
+
blocked_message_completion_column_name,
|
|
214
|
+
replaced_message_prompt_column_name,
|
|
215
|
+
replaced_message_response_column_name,
|
|
216
|
+
f"Noneed_{prompt_column_name}",
|
|
217
|
+
f"Noneed_{response_column_name}",
|
|
218
|
+
PROMPT_TOKEN_COUNT_COLUMN_NAME_FROM_USAGE,
|
|
219
|
+
RESPONSE_TOKEN_COUNT_COLUMN_NAME_FROM_USAGE,
|
|
220
|
+
AGENTIC_PIPELINE_INTERACTIONS_ATTR,
|
|
221
|
+
]
|
|
222
|
+
columns_to_remove.extend(get_all_citation_columns(result_df))
|
|
223
|
+
for column in columns_to_remove:
|
|
224
|
+
if column in result_df.columns:
|
|
225
|
+
result_df = result_df.drop(column, axis=1)
|
|
226
|
+
|
|
227
|
+
return result_df
|