SIENTIAPDE-1712
Update dependencies and refactor input filter handling for consistency - Updated sientia-dataops-library dependency version from 1.10.3 to 1.10.4 in requirements.txt. - Refactored input filter handling in the Gates class to read policy and config keys in a case-insensitive manner. - Updated test cases to ensure consistency in filter key naming conventions across various scenarios.
This commit is contained in:
@@ -118,6 +118,22 @@ class Gates(MinioManager):
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
@staticmethod
|
||||
def _read_filter_entry(config: dict[str, Any]) -> tuple[str, dict[str, Any]]:
|
||||
"""
|
||||
Read filter policy/config keys in a case-insensitive way.
|
||||
|
||||
Args:
|
||||
config (dict[str, Any]): Filter configuration dictionary.
|
||||
|
||||
Return:
|
||||
tuple[str, dict[str, Any]]: Parsed policy and config payload.
|
||||
"""
|
||||
normalized = {str(key).upper(): value for key, value in config.items()}
|
||||
policy = normalized['POLICY']
|
||||
filter_config = normalized.get('CONFIG', {})
|
||||
return policy, filter_config
|
||||
|
||||
@activity.defn(name='input_gate')
|
||||
async def input_gate(self, input_data: dict[str, Any]) -> tuple[str | None, int, str]:
|
||||
"""
|
||||
@@ -171,10 +187,11 @@ class Gates(MinioManager):
|
||||
if fil not in input_filter_functions:
|
||||
self.error(f'Filter {fil} not found', metadata)
|
||||
continue
|
||||
policy, filter_config = self._read_filter_entry(config)
|
||||
try:
|
||||
if input_filter_functions[fil](data, config['config']):
|
||||
if input_filter_functions[fil](data, filter_config):
|
||||
self.debug(f'Data not passed the input filter {fil}:{config}', metadata)
|
||||
filter_output.append(config['policy'])
|
||||
filter_output.append(policy)
|
||||
except Exception as e:
|
||||
trace = traceback.format_exc()
|
||||
await self.send_notification_async(
|
||||
@@ -235,8 +252,10 @@ class Gates(MinioManager):
|
||||
self.info('Performing mlflow response gate...', metadata)
|
||||
raw_data = input_data['data']
|
||||
filters = input_data['filters']
|
||||
|
||||
self.debug(f'Input data: \n {create_sample_dict(raw_data, max_items=5, max_depth=5)}', metadata)
|
||||
|
||||
self.debug(
|
||||
f'Input data: \n {create_sample_dict(raw_data, max_items=5, max_depth=5)}', metadata
|
||||
)
|
||||
self.debug(f'Filters: {filters}', metadata)
|
||||
|
||||
payload = MinioDataFramePayload.from_dict(raw_data)
|
||||
@@ -254,17 +273,18 @@ class Gates(MinioManager):
|
||||
for fil, config in filters.items():
|
||||
if fil not in mlflow_response_filter_functions:
|
||||
continue
|
||||
policy, filter_config = self._read_filter_entry(config)
|
||||
try:
|
||||
if mlflow_response_filter_functions[fil](status, config):
|
||||
filter_output.append(config['policy'])
|
||||
comments.append(status['message'])
|
||||
if mlflow_response_filter_functions[fil](status, filter_config):
|
||||
filter_output.append(policy)
|
||||
comments.append(status.get('message', 'Unknown MLFlow API error'))
|
||||
await self.send_notification_async(
|
||||
metadata=metadata,
|
||||
notification_id=f'{gate_type.upper()}_GATE_RESPONSE_FILTER__{fil}',
|
||||
message=data['content']['message'],
|
||||
message=status.get('message', 'Unknown MLFlow API error'),
|
||||
block='mlflow_gate',
|
||||
level=NotificationLevel.ERROR,
|
||||
attachment_content=data['content']['traceback'],
|
||||
attachment_content=status.get('traceback'),
|
||||
)
|
||||
except Exception as e:
|
||||
trace = traceback.format_exc()
|
||||
@@ -341,9 +361,10 @@ class Gates(MinioManager):
|
||||
for fil, config in filters.items():
|
||||
if fil not in mlflow_content_filter_functions:
|
||||
continue
|
||||
policy, filter_config = self._read_filter_entry(config)
|
||||
try:
|
||||
if mlflow_content_filter_functions[fil](data, config):
|
||||
filter_output.append(config['policy'])
|
||||
if mlflow_content_filter_functions[fil](data, filter_config):
|
||||
filter_output.append(policy)
|
||||
await self.send_notification_async(
|
||||
metadata=metadata,
|
||||
notification_id=f'{gate_type.upper()}_GATE_CONTENT_FILTER__{fil}',
|
||||
@@ -703,7 +724,6 @@ class Gates(MinioManager):
|
||||
|
||||
self.info(f'Writing metrics for model {metadata["model_name"]}', metadata)
|
||||
|
||||
|
||||
core_tags = {
|
||||
'pod_id': self.pod_id,
|
||||
'runtime': self.runtime,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from temporalio import activity, workflow
|
||||
|
||||
|
||||
|
||||
with workflow.unsafe.imports_passed_through():
|
||||
import traceback
|
||||
from typing import Any
|
||||
@@ -20,8 +18,9 @@ with workflow.unsafe.imports_passed_through():
|
||||
now,
|
||||
)
|
||||
from sientia_do.utils.formatters import create_sample_dict
|
||||
from laborious.utils.repository.minio_manager import MinioManager
|
||||
|
||||
from laborious.utils.models.minio_dataframe_payload import MinioDataFramePayload
|
||||
from laborious.utils.repository.minio_manager import MinioManager
|
||||
from laborious.utils.repository.model_repository import MLFlowRepository
|
||||
|
||||
|
||||
|
||||
@@ -25,9 +25,7 @@ Metric Labels:
|
||||
"""
|
||||
|
||||
from prometheus_client import Counter, Gauge, Histogram
|
||||
from sientia_do.observability.metrics import (
|
||||
CORE_LABELS
|
||||
)
|
||||
from sientia_do.observability.metrics import CORE_LABELS
|
||||
|
||||
# Application health metric
|
||||
APP_UP = Gauge(
|
||||
|
||||
@@ -162,7 +162,7 @@ class MinioDataFramePayload:
|
||||
"""
|
||||
Return True if the payload has some data internally or in MinIO.
|
||||
"""
|
||||
return (self.data is not None and not self.data != {}) or self.object_key is not None
|
||||
return (self.data is not None and self.data != {}) or self.object_key is not None
|
||||
|
||||
@classmethod
|
||||
async def from_dataframe(
|
||||
@@ -200,7 +200,6 @@ class MinioDataFramePayload:
|
||||
data=None, last_timestamp=now().strftime(DATETIME_FORMAT_WITH_TZ), status=status
|
||||
)
|
||||
|
||||
|
||||
if last_timestamp is None:
|
||||
last_timestamp = max(dataframe['timestamp'].values.tolist())
|
||||
|
||||
|
||||
@@ -219,20 +219,21 @@ async def main():
|
||||
|
||||
logger.custom_info('Workers started successfully', metadata)
|
||||
|
||||
exit_code = 0
|
||||
try:
|
||||
# This will run the workers and wait for them to complete.
|
||||
# If an exception occurs in any of the worker handlers, it will be propagated here.
|
||||
await asyncio.gather(*handlers)
|
||||
except BaseException as e: # NOSONAR
|
||||
logger.custom_error(f'An unhandled exception occurred: {e}', metadata)
|
||||
exit_code = 1
|
||||
finally:
|
||||
if notification_handler:
|
||||
notification_handler.shutdown()
|
||||
if activities:
|
||||
await activities.shutdown()
|
||||
# Exit with a non-zero status code to indicate failure to Kubernetes
|
||||
metrics.APP_UP.labels(pod_id=POD_ID).set(0) # Mark app as DOWN
|
||||
sys.exit(1)
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
def start_prometheus_server():
|
||||
|
||||
@@ -46,7 +46,7 @@ class Drift:
|
||||
ORDER BY timestamp ASC
|
||||
""" # nosec B608 - values come from internal Temporal workflow config, not user input
|
||||
|
||||
target_data_handler = workflow.start_local_activity_method(
|
||||
target_data_handler = workflow.start_activity_method(
|
||||
Activities.load_custom_query,
|
||||
{
|
||||
**metadata,
|
||||
@@ -58,7 +58,7 @@ class Drift:
|
||||
start_to_close_timeout=timedelta(seconds=300),
|
||||
)
|
||||
|
||||
reference_data_handler = workflow.start_local_activity_method(
|
||||
reference_data_handler = workflow.start_activity_method(
|
||||
Activities.get_reference_data,
|
||||
{**metadata, 'model_name': input_data['model_name']},
|
||||
retry_policy=retry_policy,
|
||||
|
||||
@@ -7,6 +7,7 @@ with workflow.unsafe.imports_passed_through():
|
||||
from sientia_do.temporal.policies import retry_policy
|
||||
|
||||
from laborious.activities.activities import Activities
|
||||
from laborious.utils.models.minio_dataframe_payload import MinioDataFramePayload
|
||||
|
||||
|
||||
@workflow.defn(name='minimal_retrain')
|
||||
@@ -84,7 +85,8 @@ class MinimalRetrain:
|
||||
start_to_close_timeout=timedelta(seconds=600),
|
||||
)
|
||||
|
||||
if not storage_result.has_data():
|
||||
storage_payload = MinioDataFramePayload.from_dict(storage_result)
|
||||
if not storage_payload.has_data():
|
||||
raise ValueError('No data returned from query')
|
||||
|
||||
experiment_response = await workflow.execute_activity_method(
|
||||
|
||||
@@ -105,12 +105,14 @@ class PredictionsBatch:
|
||||
'transform_table_name': input_data['transform_table_name'],
|
||||
'model_id': input_data['model_id'],
|
||||
'model_name': input_data['model_name'],
|
||||
'input_filters': input_data.get('input_filters', {'EMPTY_DATA': {'POLICY': 'STOP'}}),
|
||||
'input_filters': input_data.get(
|
||||
'input_filters', {'EMPTY_DATA': {'POLICY': 'STOP', 'CONFIG': {}}}
|
||||
),
|
||||
'mlflow_transform_filters': input_data.get(
|
||||
'mlflow_transform_filters', {'API_ERROR': {'POLICY': 'STOP'}}
|
||||
'mlflow_transform_filters', {'API_ERROR': {'POLICY': 'STOP', 'CONFIG': {}}}
|
||||
),
|
||||
'mlflow_predict_filters': input_data.get(
|
||||
'mlflow_predict_filters', {'API_ERROR': {'POLICY': 'STOP'}}
|
||||
'mlflow_predict_filters', {'API_ERROR': {'POLICY': 'STOP', 'CONFIG': {}}}
|
||||
),
|
||||
'model_config': input_data.get('model_config', {}),
|
||||
'path_priority': input_data.get('path_priority', ['STOP', 'CONTINUE', 'REPEAT']),
|
||||
|
||||
@@ -47,7 +47,7 @@ class SimpleMetrics:
|
||||
p."timestamp" desc;
|
||||
""" # nosec B608 - values come from internal Temporal workflow config, not user input
|
||||
|
||||
target_data = await workflow.execute_local_activity_method(
|
||||
target_data = await workflow.execute_activity_method(
|
||||
Activities.load_custom_query,
|
||||
{
|
||||
**metadata,
|
||||
|
||||
@@ -7,7 +7,6 @@ with workflow.unsafe.imports_passed_through():
|
||||
from sientia_do.temporal.policies import retry_policy
|
||||
|
||||
from laborious.activities.activities import Activities
|
||||
from laborious.utils.models.minio_dataframe_payload import MinioDataFramePayload
|
||||
|
||||
|
||||
@workflow.defn(name='subworkflow.prediction_process')
|
||||
@@ -112,7 +111,6 @@ class PredictionProcess:
|
||||
start_to_close_timeout=timedelta(minutes=5),
|
||||
)
|
||||
raise e
|
||||
|
||||
|
||||
async def _run_prediction_pipeline(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user