diff --git a/kairon/actions/definitions/schedule.py b/kairon/actions/definitions/schedule.py index 82aedc59c..786265bd7 100644 --- a/kairon/actions/definitions/schedule.py +++ b/kairon/actions/definitions/schedule.py @@ -25,6 +25,7 @@ from kairon.shared.actions.models import ActionType from kairon.shared.actions.utils import ActionUtility from kairon.shared.callback.data_objects import CallbackConfig +from kairon.shared.data.constant import TASK_TYPE class ActionSchedule(ActionsBase): @@ -136,12 +137,13 @@ async def add_schedule_job(self, date_time: datetime, data: Dict, timezone: Text, - kwargs=None): + **kwargs): func = obj_to_ref(ExecutorFactory.get_executor().execute_task) _id = uuid7().hex data['predefined_objects']['event'] = _id args = (func, "scheduler_evaluator", data,) + kwargs.update({'task_type': TASK_TYPE.ACTION.value}) trigger = DateTrigger(run_date=date_time, timezone=timezone) next_run_time = trigger.get_next_fire_time(None, datetime.now(astimezone(timezone) or get_localzone())) diff --git a/kairon/actions/definitions/web_search.py b/kairon/actions/definitions/web_search.py index 837d350fe..bc89cc1e9 100644 --- a/kairon/actions/definitions/web_search.py +++ b/kairon/actions/definitions/web_search.py @@ -64,7 +64,8 @@ async def execute(self, dispatcher: CollectingDispatcher, tracker: Tracker, doma if not ActionUtility.is_empty(latest_msg): results = ActionUtility.perform_web_search(latest_msg, topn=action_config.get("topn"), - website=action_config.get("website")) + website=action_config.get("website"), + bot=self.bot) if results: bot_response = ActionUtility.format_search_result(results) if not ActionUtility.is_empty(action_config.get('set_slot')): diff --git a/kairon/api/app/routers/bot/bot.py b/kairon/api/app/routers/bot/bot.py index 94858f3e0..74b1902d5 100644 --- a/kairon/api/app/routers/bot/bot.py +++ b/kairon/api/app/routers/bot/bot.py @@ -38,6 +38,7 @@ from kairon.shared.data.processor import MongoProcessor from kairon.shared.data.training_data_generation_processor import TrainingDataGenerationProcessor from kairon.shared.data.utils import DataUtility +from kairon.shared.events.processor import ExecutorProcessor from kairon.shared.importer.data_objects import ValidationLogs from kairon.shared.importer.processor import DataImporterLogProcessor from kairon.shared.live_agent.live_agent import LiveAgentHandler @@ -1704,6 +1705,27 @@ async def get_llm_logs( return Response(data=data) +@router.get("/executor/logs", response_model=Response) +async def get_executor_logs( + start_idx: int = 0, page_size: int = 10, + event_class: str = None, task_type: str = None, + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=TESTER_ACCESS) +): + """ + Get executor logs data based on filters provided. + """ + logs = list(ExecutorProcessor.get_executor_logs(current_user.get_bot(), start_idx, page_size, + event_class=event_class, task_type=task_type)) + row_cnt = ExecutorProcessor.get_row_count(current_user.get_bot(), + event_class=event_class, + task_type=task_type) + data = { + "logs": logs, + "total": row_cnt + } + return Response(data=data) + + @router.get("/metadata/llm", response_model=Response) async def get_llm_metadata( current_user: User = Security(Authentication.get_current_user_and_bot, scopes=TESTER_ACCESS)) -> Response: diff --git a/kairon/events/executors/base.py b/kairon/events/executors/base.py index bd4800af1..4b9676360 100644 --- a/kairon/events/executors/base.py +++ b/kairon/events/executors/base.py @@ -1,5 +1,5 @@ from abc import abstractmethod - +from typing import Any from kairon.shared.constants import EventClass from kairon.shared.data.constant import EVENT_STATUS, TASK_TYPE @@ -13,7 +13,7 @@ class ExecutorBase: def execute_task(self, event_class: EventClass, data: dict, **kwargs): raise NotImplementedError("Provider not implemented") - def log_task(self, event_class: EventClass, task_type: TASK_TYPE, data: dict, status: EVENT_STATUS, **kwargs): + def log_task(self, event_class: EventClass, task_type: TASK_TYPE, data: Any, status: EVENT_STATUS, **kwargs): from bson import ObjectId from kairon.shared.cloud.utils import CloudUtility diff --git a/kairon/shared/actions/utils.py b/kairon/shared/actions/utils.py index a8c9f12b0..cf8a5982c 100644 --- a/kairon/shared/actions/utils.py +++ b/kairon/shared/actions/utils.py @@ -490,7 +490,7 @@ def perform_web_search(search_term: str, **kwargs): trigger_task = Utility.environment['web_search']['trigger_task'] search_engine_url = Utility.environment['web_search']['url'] website = kwargs.get('website') if kwargs.get('website') else '' - request_body = {"text": search_term, "site": website, "topn": kwargs.get("topn")} + request_body = {"text": search_term, "site": website, "topn": kwargs.get("topn"), "bot": kwargs.get("bot")} results = [] try: if trigger_task: diff --git a/kairon/shared/cloud/utils.py b/kairon/shared/cloud/utils.py index b1004eaae..698212f3b 100644 --- a/kairon/shared/cloud/utils.py +++ b/kairon/shared/cloud/utils.py @@ -1,3 +1,5 @@ +from typing import Any + import ujson as json import os import time @@ -57,7 +59,7 @@ def delete_file(bucket, file): s3.delete_object(Bucket=bucket, Key=file) @staticmethod - def trigger_lambda(event_class: EventClass, env_data: dict, task_type: TASK_TYPE = TASK_TYPE.ACTION.value, + def trigger_lambda(event_class: EventClass, env_data: Any, task_type: TASK_TYPE = TASK_TYPE.ACTION.value, from_executor: bool = False): """ Triggers lambda based on the event class. @@ -103,6 +105,9 @@ def log_task(event_class: EventClass, task_type: TASK_TYPE, data: dict, status: from kairon.shared.events.data_objects import ExecutorLogs executor_log_id = kwargs.get("executor_log_id") if kwargs.get("executor_log_id") else ObjectId().__str__() + bot_id = CloudUtility.get_bot_id_from_env_data(event_class, data, + from_executor=kwargs.get("from_executor", False), + task_type=task_type) try: log = ExecutorLogs.objects(executor_log_id=executor_log_id, task_type=task_type, event_class=event_class, @@ -116,9 +121,47 @@ def log_task(event_class: EventClass, task_type: TASK_TYPE, data: dict, status: for key, value in kwargs.items(): if not getattr(log, key, None) and Utility.is_picklable_for_mongo({key: value}): setattr(log, key, value) + log.bot = bot_id log.save() return executor_log_id + @staticmethod + def get_bot_id_from_env_data(event_class: EventClass, data: Any, **kwargs): + bot = None + from_executor = kwargs.get("from_executor") + + if isinstance(data, dict) and 'bot' in data: + bot = data['bot'] + + elif event_class == EventClass.web_search: + bot = data.get('bot') + + elif event_class == EventClass.pyscript_evaluator: + predefined_objects = data.get('predefined_objects', {}) + + if 'slot' in predefined_objects and 'bot' in predefined_objects['slot']: + bot = predefined_objects['slot']['bot'] + + task_type = kwargs.get("task_type") + if task_type == "Callback" and 'bot' in predefined_objects: + bot = predefined_objects['bot'] + + elif event_class == EventClass.scheduler_evaluator and isinstance(data, list): + for item in data: + if item.get('name') == 'PREDEFINED_OBJECTS': + predefined_objects = item.get('value', {}) + if 'bot' in predefined_objects: + bot = predefined_objects['bot'] + break + + elif from_executor and isinstance(data, list): + for item in data: + if item.get('name') == 'BOT': + bot = item.get('value') + break + + return bot + @staticmethod def lambda_execution_failed(response): return (response['StatusCode'] != 200 or diff --git a/kairon/shared/events/processor.py b/kairon/shared/events/processor.py new file mode 100644 index 000000000..c0954822e --- /dev/null +++ b/kairon/shared/events/processor.py @@ -0,0 +1,41 @@ +from kairon.shared.events.data_objects import ExecutorLogs + + +class ExecutorProcessor: + + @staticmethod + def get_executor_logs(bot: str, start_idx: int = 0, page_size: int = 10, **kwargs): + """ + Get all executor logs data . + @param bot: bot id. + @param start_idx: start index + @param page_size: page size + @return: list of logs. + """ + event_class = kwargs.get("event_class") + task_type = kwargs.get("task_type") + query = {"bot": bot} + if event_class: + query.update({"event_class": event_class}) + if task_type: + query.update({"task_type": task_type}) + for log in ExecutorLogs.objects(**query).order_by("-timestamp").skip(start_idx).limit(page_size): + executor_logs = log.to_mongo().to_dict() + executor_logs.pop('_id') + yield executor_logs + + @staticmethod + def get_row_count(bot: str, **kwargs): + """ + Gets the count of rows in a ExecutorLogs for a particular bot. + :param bot: bot id + :return: Count of rows + """ + event_class = kwargs.get("event_class") + task_type = kwargs.get("task_type") + query = {"bot": bot} + if event_class: + query.update({"event_class": event_class}) + if task_type: + query.update({"task_type": task_type}) + return ExecutorLogs.objects(**query).count() diff --git a/tests/integration_test/action_service_test.py b/tests/integration_test/action_service_test.py index ff4ec48cc..af2eebe9f 100644 --- a/tests/integration_test/action_service_test.py +++ b/tests/integration_test/action_service_test.py @@ -8312,7 +8312,7 @@ def test_process_web_search_action(): def _perform_web_search(*args, **kwargs): assert args == ('What is data?',) - assert kwargs == {'topn': 1, 'website': 'https://www.w3schools.com/'} + assert kwargs == {'topn': 1, 'website': 'https://www.w3schools.com/', 'bot': bot} return [{ 'title': 'Data Science Introduction - W3Schools', 'text': "Data Science is a combination of multiple disciplines that uses statistics, data analysis, and machine learning to analyze data and to extract knowledge and insights from it. What is Data Science? Data Science is about data gathering, analysis and decision-making.", @@ -8437,7 +8437,7 @@ def test_process_web_search_action_with_search_engine_url(): status=200, match=[ responses.matchers.json_params_matcher({ - "text": 'What is data science?', "site": '', "topn": 1 + "text": 'What is data science?', "site": '', "topn": 1, 'bot': bot })], ) @@ -8545,7 +8545,7 @@ def test_process_web_search_action_with_kairon_user_msg_entity(): def _perform_web_search(*args, **kwargs): assert args == ('my public search text',) - assert kwargs == {'topn': 2, 'website': None} + assert kwargs == {'topn': 2, 'website': None, 'bot': bot} return [ {'title': 'What is Data Science? | IBM', 'text': 'Data science combines math, statistics, programming, analytics, AI, and machine learning to uncover insights from data. Learn how data science works, what it entails, and how it differs from data science and BI.', @@ -8665,7 +8665,7 @@ def test_process_web_search_action_without_kairon_user_msg_entity(): def _perform_web_search(*args, **kwargs): assert args == ('/action_public_search',) - assert kwargs == {'topn': 2, 'website': None} + assert kwargs == {'topn': 2, 'website': None, 'bot': bot} return [ {'title': 'What is Data Science? | IBM', 'text': 'Data science combines math, statistics, programming, analytics, AI, and machine learning to uncover insights from data. Learn how data science works, what it entails, and how it differs from data science and BI.', @@ -8785,7 +8785,7 @@ def test_process_web_search_action_dispatch_false(): def _perform_web_search(*args, **kwargs): assert args == ('What is Python?',) - assert kwargs == {'topn': 1, 'website': None} + assert kwargs == {'topn': 1, 'website': None, 'bot': bot} return [ {'title': 'Python.org - What is Python? Executive Summary', 'text': 'Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. Its high-level built in data structures, combined with dynamic typing and dynamic binding, make it very attractive for Rapid Application Development, as well as for use as a scripting or glue language to connect existing components together.', diff --git a/tests/integration_test/services_test.py b/tests/integration_test/services_test.py index dca4792c9..8284c7c44 100644 --- a/tests/integration_test/services_test.py +++ b/tests/integration_test/services_test.py @@ -57,7 +57,7 @@ KAIRON_TWO_STAGE_FALLBACK, FeatureMappings, DEFAULT_NLU_FALLBACK_RESPONSE, - DEFAULT_LLM + DEFAULT_LLM, TASK_TYPE ) from kairon.shared.data.data_objects import ( Stories, @@ -2354,6 +2354,339 @@ def test_upload_with_bot_content_event_append_validate_payload_data(): bot_settings.save() +@pytest.fixture() +def get_executor_logs(): + from kairon.events.executors.base import ExecutorBase + executor = ExecutorBase() + executor.log_task(event_class=EventClass.model_training.value, task_type=TASK_TYPE.EVENT.value, + data=[{"name": "BOT", "value": pytest.bot}, {"name": "USER", "value": "test_user"}], + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.INITIATED.value, + from_executor=True) + executor.log_task(event_class=EventClass.model_testing.value, task_type=TASK_TYPE.EVENT.value, + data=[{"name": "BOT", "value": pytest.bot}, {"name": "USER", "value": "test_user"}], + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.INITIATED.value, + from_executor=True) + executor.log_task(event_class=EventClass.scheduler_evaluator.value, task_type=TASK_TYPE.ACTION.value, + data=[ + { + "name": "SOURCE_CODE", + "value": "import requests\n\nurl=\"https://waba-v2.360dialog.io/messages\"\n\nheaders={'Content-Type': 'application/json', 'D360-API-KEY' : '6aYs394839849384rkU6Bs39R3RAK'}\n\ncontacts = ['919657099999','918210099999']\ncontacts = [\"919515999999\"]\nbody = {\n \"messaging_product\": \"whatsapp\",\n \"recipient_type\": \"individual\",\n \"to\": \"9199991685\",\n \"type\": \"template\",\n \"template\": {\n \"namespace\": \"54500467_f322_4595_becd_41555889bfd8\",\n \"language\": {\n \"policy\": \"deterministic\",\n \"code\": \"en\"\n },\n \"name\": \"schedule_action_test\"\n }\n}\n\nfor contact in contacts:\n body[\"to\"] = contact\n resp = requests.post(url, headers=headers, data=json.dumps(body))\n resp = resp.json()\n print(resp[\"messages\"])\n\nbot_response = 'this from callback pyscript'" + }, + { + "name": "PREDEFINED_OBJECTS", + "value": { + "val1": "rajan", + "val2": "hitesh", + "passwd": None, + "myuser": "mahesh.sattala@digite.com", + "bot": pytest.bot, + "event": "01928ec7f495717b842dfdjfkdc65a161e3" + } + } + ], + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.FAIL.value, + exception="An error occurred (UnrecognizedClientException) when calling the Invoke operation: The security token included in the request is invalid.") + executor.log_task(event_class=EventClass.pyscript_evaluator.value, task_type=TASK_TYPE.ACTION.value, + data={ + "source_code": "bot_response = \"Test\"", + "predefined_objects": { + "sender_id": "mahesh.sattala@digite.com", + "user_message": "/pyscript", + "slot": { + "calbkslot1": None, + "calbkslot2": None, + "quick_reply": None, + "latitude": None, + "longitude": None, + "doc_url": None, + "document": None, + "video": None, + "audio": None, + "image": None, + "http_status_code": None, + "flow_reply": None, + "order": None, + "bot": pytest.bot, + "kairon_action_response": "Test", + "session_started_metadata": { + "tabname": "default", + "displayLabel": "", + "telemetry-uid": "None", + "telemetry-sid": "None", + "is_integration_user": False, + "bot": pytest.bot, + "account": 8, + "channel_type": "chat_client" + } + }, + "intent": "pyscript", + "chat_log": [ + { + "user": "hi" + }, + { + "bot": { + "text": "Let me be your AI Assistant and provide you with service", + "elements": None, + "quick_replies": None, + "buttons": None, + "attachment": None, + "image": None, + "custom": None + } + }, + { + "user": "trigger callback" + }, + { + "bot": { + "text": "callbk triggered", + "elements": None, + "quick_replies": None, + "buttons": None, + "attachment": None, + "image": None, + "custom": None + } + }, + { + "user": "/pyscript" + } + ], + "key_vault": {}, + "latest_message": { + "intent": { + "name": "pyscript", + "confidence": 1 + }, + "entities": [], + "text": "/pyscript", + "message_id": "ee57dde3c007448eb257d6b5e20c30ac", + "metadata": { + "tabname": "default", + "displayLabel": "", + "telemetry-uid": "None", + "telemetry-sid": "None", + "is_integration_user": False, + "bot": pytest.bot, + "account": 8, + "channel_type": "chat_client" + }, + "intent_ranking": [ + { + "name": "pyscript", + "confidence": 1 + } + ] + }, + "kairon_user_msg": None, + "session_started": None + } + }, + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.INITIATED.value) + executor.log_task(event_class=EventClass.pyscript_evaluator.value, task_type=TASK_TYPE.CALLBACK.value, + data={ + "source_code": "bot_response = \"test - this is from callback test\"", + "predefined_objects": { + "req": { + "type": "GET", + "body": None, + "params": {} + }, + "req_host": "127.0.0.1", + "action_name": "clbk1", + "callback_name": "test", + "bot": pytest.bot, + "sender_id": "mahesh.sattala@digite.com", + "channel": "unsupported (None)", + "metadata": { + "first_name": "rajan", + "bot": pytest.bot + }, + "identifier": "01928eb52813799e81c56803ecf39e6e", + "callback_url": "http://localhost:5059/callback/d/01928eb52813799e81c56803ecf39e6e/98bxWAMY9HF40La5AKQjEb-0JqaDTKX_Mmmmmmmmmmm", + "execution_mode": "async", + "state": 0, + "is_valid": True + } + }, + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.COMPLETED.value, + response={ + "ResponseMetadata": { + "RequestId": "0da1cdd1-1702-473b-8109-67a39f990835", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "date": "Tue, 15 Oct 2024 07:38:01 GMT", + "content-type": "application/json", + "content-length": "740", + "connection": "keep-alive", + "x-amzn-requestid": "0da1cdd1-1702-473b-8109-67a39f9sdlksldksl", + "x-amzn-remapped-content-length": "0", + "x-amz-executed-version": "$LATEST", + "x-amz-log-result": "sdskjdksjdkjskdjskjdksj", + "x-amzn-trace-id": "Root=1-670e1bd6-25e7667c7c6ce37f09a9afc7;Sampled=1;Lineage=1:d072fc6c:0" + }, + "RetryAttempts": 0 + }, + "StatusCode": 200, + "LogResult": "sldklskdlskdlskdlk", + "ExecutedVersion": "$LATEST", + "Payload": { + "statusCode": 200, + "statusDescription": "200 OK", + "isBase64Encoded": False, + "headers": { + "Content-Type": "text/html; charset=utf-8" + }, + "body": { + "req": { + "type": "GET", + "body": None, + "params": {} + }, + "req_host": "127.0.0.1", + "action_name": "clbk1", + "callback_name": "test", + "bot": pytest.bot, + "sender_id": "mahesh.sattala@digite.com", + "channel": "unsupported (None)", + "metadata": { + "first_name": "rajan", + "bot": pytest.bot + }, + "identifier": "01928eb52813799e81c56803ecf39e6e", + "callback_url": "http://localhost:5059/callback/d/01928eb52813799e81c56803ecf39e6e/98bxWAMY9HF40La5AKQjEb-0JqaDTKX_Mmmmmmmmmmm", + "execution_mode": "async", + "state": 0, + "is_valid": True, + "bot_response": "test - this is from callback test" + } + } + } + ) + + +def test_get_executor_logs(get_executor_logs): + response = client.get( + url=f"/api/bot/{pytest.bot}/executor/logs?task_type=Callback", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["error_code"] == 0 + assert not actual["message"] + assert actual["success"] + assert len(actual["data"]["logs"]) == actual["data"]["total"] == 1 + assert actual["data"]["logs"][0]["task_type"] == "Callback" + assert actual["data"]["logs"][0]["event_class"] == "pyscript_evaluator" + assert actual["data"]["logs"][0]["status"] == "Completed" + assert actual["data"]["logs"][0]["data"] == { + 'source_code': 'bot_response = "test - this is from callback test"', + 'predefined_objects': { + 'req': {'type': 'GET', 'body': None, 'params': {}}, + 'req_host': '127.0.0.1', 'action_name': 'clbk1', + 'callback_name': 'test', 'bot': pytest.bot, + 'sender_id': 'mahesh.sattala@digite.com', + 'channel': 'unsupported (None)', + 'metadata': {'first_name': 'rajan', + 'bot': pytest.bot}, + 'identifier': '01928eb52813799e81c56803ecf39e6e', + 'callback_url': 'http://localhost:5059/callback/d/01928eb52813799e81c56803ecf39e6e/98bxWAMY9HF40La5AKQjEb-0JqaDTKX_Mmmmmmmmmmm', + 'execution_mode': 'async', 'state': 0, 'is_valid': True} + } + assert actual["data"]["logs"][0]["response"] == { + 'ResponseMetadata': {'RequestId': '0da1cdd1-1702-473b-8109-67a39f990835', 'HTTPStatusCode': 200, + 'HTTPHeaders': {'date': 'Tue, 15 Oct 2024 07:38:01 GMT', + 'content-type': 'application/json', 'content-length': '740', + 'connection': 'keep-alive', + 'x-amzn-requestid': '0da1cdd1-1702-473b-8109-67a39f9sdlksldksl', + 'x-amzn-remapped-content-length': '0', 'x-amz-executed-version': '$LATEST', + 'x-amz-log-result': 'sdskjdksjdkjskdjskjdksj', + 'x-amzn-trace-id': 'Root=1-670e1bd6-25e7667c7c6ce37f09a9afc7;Sampled=1;Lineage=1:d072fc6c:0'}, + 'RetryAttempts': 0}, 'StatusCode': 200, 'LogResult': 'sldklskdlskdlskdlk', + 'ExecutedVersion': '$LATEST', + 'Payload': {'statusCode': 200, 'statusDescription': '200 OK', 'isBase64Encoded': False, + 'headers': {'Content-Type': 'text/html; charset=utf-8'}, + 'body': {'req': {'type': 'GET', 'body': None, 'params': {}}, 'req_host': '127.0.0.1', + 'action_name': 'clbk1', 'callback_name': 'test', 'bot': pytest.bot, + 'sender_id': 'mahesh.sattala@digite.com', 'channel': 'unsupported (None)', + 'metadata': {'first_name': 'rajan', 'bot': pytest.bot}, + 'identifier': '01928eb52813799e81c56803ecf39e6e', + 'callback_url': 'http://localhost:5059/callback/d/01928eb52813799e81c56803ecf39e6e/98bxWAMY9HF40La5AKQjEb-0JqaDTKX_Mmmmmmmmmmm', + 'execution_mode': 'async', 'state': 0, 'is_valid': True, + 'bot_response': 'test - this is from callback test'}}} + assert actual["data"]["logs"][0]["executor_log_id"] + assert actual["data"]["logs"][0]['bot'] == pytest.bot + + response = client.get( + url=f"/api/bot/{pytest.bot}/executor/logs?task_type=Event", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["error_code"] == 0 + assert not actual["message"] + assert actual["success"] + assert len(actual["data"]["logs"]) == actual["data"]["total"] == 2 + assert actual["data"]["logs"][0]["task_type"] == "Event" + assert actual["data"]["logs"][0]["event_class"] == "model_testing" + assert actual["data"]["logs"][0]["status"] == "Initiated" + assert actual["data"]["logs"][0]["data"] == [ + { + 'name': 'BOT', + 'value': pytest.bot + }, + { + 'name': 'USER', + 'value': 'test_user' + } + ] + assert actual["data"]["logs"][0]["executor_log_id"] + assert actual["data"]["logs"][0]['from_executor'] is True + assert actual["data"]["logs"][0]['bot'] == pytest.bot + + assert actual["data"]["logs"][1]["task_type"] == "Event" + assert actual["data"]["logs"][1]["event_class"] == "model_training" + assert actual["data"]["logs"][1]["status"] == "Initiated" + assert actual["data"]["logs"][1]["data"] == [ + { + 'name': 'BOT', + 'value': pytest.bot + }, + { + 'name': 'USER', + 'value': 'test_user' + } + ] + assert actual["data"]["logs"][1]["executor_log_id"] + assert actual["data"]["logs"][1]['from_executor'] is True + assert actual["data"]["logs"][1]['bot'] == pytest.bot + + response = client.get( + url=f"/api/bot/{pytest.bot}/executor/logs?task_type=Event&event_class=model_training", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["error_code"] == 0 + assert not actual["message"] + assert actual["success"] + assert len(actual["data"]["logs"]) == actual["data"]["total"] == 1 + assert actual["data"]["logs"][0]["task_type"] == "Event" + assert actual["data"]["logs"][0]["event_class"] == "model_training" + assert actual["data"]["logs"][0]["status"] == "Initiated" + assert actual["data"]["logs"][0]["data"] == [ + { + 'name': 'BOT', + 'value': pytest.bot + }, + { + 'name': 'USER', + 'value': 'test_user' + } + ] + assert actual["data"]["logs"][0]["executor_log_id"] + assert actual["data"]["logs"][0]['from_executor'] is True + assert actual["data"]["logs"][0]['bot'] == pytest.bot + + def test_get_collection_data_with_no_collection_data(): response = client.get( url=f"/api/bot/{pytest.bot}/data/collection", diff --git a/tests/unit_test/action/action_test.py b/tests/unit_test/action/action_test.py index a43fe123b..e4803c62d 100644 --- a/tests/unit_test/action/action_test.py +++ b/tests/unit_test/action/action_test.py @@ -3078,6 +3078,7 @@ def test_public_search_action(self, mock_trigger_lambda): search_term = "What is data science?" website = "https://www.w3schools.com/" topn = 1 + bot = "test_bot" mock_trigger_lambda.return_value = { "ResponseMetadata": { @@ -3115,19 +3116,21 @@ def test_public_search_action(self, mock_trigger_lambda): } mock_environment = {"web_search": {"trigger_task": True, 'url': None}} with patch("kairon.shared.utils.Utility.environment", new=mock_environment): - result = ActionUtility.perform_web_search(search_term, topn=topn, website=website) + result = ActionUtility.perform_web_search(search_term, topn=topn, website=website, bot=bot) assert result == [{ 'title': 'Data Science Introduction - W3Schools', 'text': "Data Science is a combination of multiple disciplines that uses statistics, data analysis, and machine learning to analyze data and to extract knowledge and insights from it. What is Data Science? Data Science is about data gathering, analysis and decision-making.", 'link': 'https://www.w3schools.com/datascience/ds_introduction.asp' }] called_args = mock_trigger_lambda.call_args - assert called_args.args[1] == {'text': 'What is data science?', 'site': 'https://www.w3schools.com/', 'topn': 1} + assert called_args.args[1] == {'text': 'What is data science?', 'site': 'https://www.w3schools.com/', + 'topn': 1, 'bot': bot} @mock.patch("kairon.shared.cloud.utils.CloudUtility.trigger_lambda", autospec=True) def test_public_search_action_without_website(self, mock_trigger_lambda): search_term = "What is AI?" topn = 2 + bot = "test_bot" mock_trigger_lambda.return_value = { "ResponseMetadata": { @@ -3170,7 +3173,7 @@ def test_public_search_action_without_website(self, mock_trigger_lambda): mock_environment = {"web_search": {"trigger_task": True, 'url': None}} with patch("kairon.shared.utils.Utility.environment", new=mock_environment): - result = ActionUtility.perform_web_search(search_term, topn=topn) + result = ActionUtility.perform_web_search(search_term, topn=topn, bot=bot) assert result == [ {'title': 'Artificial intelligence - Wikipedia', 'text': 'Artificial intelligence ( AI) is the intelligence of machines or software, as opposed to the intelligence of human beings or animals.', @@ -3180,7 +3183,7 @@ def test_public_search_action_without_website(self, mock_trigger_lambda): 'link': 'https://www.britannica.com/technology/artificial-intelligence'} ] called_args = mock_trigger_lambda.call_args - assert called_args.args[1] == {'text': 'What is AI?', 'site': '', 'topn': 2} + assert called_args.args[1] == {'text': 'What is AI?', 'site': '', 'topn': 2, 'bot': bot} @mock.patch("kairon.shared.cloud.utils.CloudUtility.trigger_lambda", autospec=True) def test_public_search_action_exception_lambda(self, mock_trigger_lambda): @@ -3239,6 +3242,7 @@ def test_public_search_with_url(self): search_term = "What is AI?" topn = 1 search_engine_url = "https://duckduckgo.com/" + bot = "test_bot" responses.add( method=responses.POST, url=search_engine_url, @@ -3250,12 +3254,12 @@ def test_public_search_with_url(self): status=200, match=[ responses.matchers.json_params_matcher({ - "text": 'What is AI?', "site": '', "topn": 1 + "text": 'What is AI?', "site": '', "topn": 1, "bot": bot })], ) with mock.patch.dict(Utility.environment, {'web_search': {"trigger_task": False, "url": search_engine_url}}): - result = ActionUtility.perform_web_search(search_term, topn=topn) + result = ActionUtility.perform_web_search(search_term, topn=topn, bot=bot) assert result == [ {'title': 'Artificial intelligence - Wikipedia', 'text': 'Artificial intelligence ( AI) is the intelligence of machines or software, as opposed to the intelligence of human beings or animals.', @@ -3266,6 +3270,7 @@ def test_public_search_with_url(self): def test_public_search_with_url_with_site(self): search_term = "What is AI?" topn = 1 + bot = "test_bot" website = "https://en.wikipedia.org/" search_engine_url = "https://duckduckgo.com/" responses.add( @@ -3279,12 +3284,12 @@ def test_public_search_with_url_with_site(self): status=200, match=[ responses.matchers.json_params_matcher({ - "text": 'What is AI?', "site": 'https://en.wikipedia.org/', "topn": 1 + "text": 'What is AI?', "site": 'https://en.wikipedia.org/', "topn": 1, "bot": bot })], ) with mock.patch.dict(Utility.environment, {'web_search': {"trigger_task": False, "url": search_engine_url}}): - result = ActionUtility.perform_web_search(search_term, topn=topn, website=website) + result = ActionUtility.perform_web_search(search_term, topn=topn, website=website, bot=bot) assert result == [ {'title': 'Artificial intelligence - Wikipedia', 'text': 'Artificial intelligence ( AI) is the intelligence of machines or software, as opposed to the intelligence of human beings or animals.', @@ -3295,6 +3300,7 @@ def test_public_search_with_url_with_site(self): def test_public_search_with_url_exception(self): search_term = "What is AI?" topn = 1 + bot = "test_bot" search_engine_url = "https://duckduckgo.com/" responses.add( method=responses.POST, @@ -3303,18 +3309,19 @@ def test_public_search_with_url_exception(self): status=500, match=[ responses.matchers.json_params_matcher({ - "text": 'What is AI?', "site": '', "topn": 1 + "text": 'What is AI?', "site": '', "topn": 1, "bot": bot })], ) with mock.patch.dict(Utility.environment, {'web_search': {"trigger_task": False, "url": search_engine_url}}): with pytest.raises(ActionFailure, match=re.escape('Failed to execute the url: Got non-200 status code: 500 {"data": []}')): - ActionUtility.perform_web_search(search_term, topn=topn) + ActionUtility.perform_web_search(search_term, topn=topn, bot=bot) @responses.activate def test_public_search_with_url_exception_error_code_not_zero(self): search_term = "What is AI?" topn = 1 + bot = "test_bot" search_engine_url = "https://duckduckgo.com/" result = {'success': False, 'data': [], 'error_code': 422} responses.add( @@ -3324,13 +3331,13 @@ def test_public_search_with_url_exception_error_code_not_zero(self): status=200, match=[ responses.matchers.json_params_matcher({ - "text": 'What is AI?', "site": '', "topn": 1 + "text": 'What is AI?', "site": '', "topn": 1, "bot": bot })], ) with mock.patch.dict(Utility.environment, {'web_search': {"trigger_task": False, "url": search_engine_url}}): with pytest.raises(ActionFailure, match=re.escape(f"{result}")): - ActionUtility.perform_web_search(search_term, topn=topn) + ActionUtility.perform_web_search(search_term, topn=topn, bot=bot) @mock.patch("kairon.shared.cloud.utils.CloudUtility.trigger_lambda", autospec=True) def test_public_search_action_app_exception(self, mock_trigger_lambda): diff --git a/tests/unit_test/cloud_utils_test.py b/tests/unit_test/cloud_utils_test.py index 64058aba2..361fe9bcc 100644 --- a/tests/unit_test/cloud_utils_test.py +++ b/tests/unit_test/cloud_utils_test.py @@ -173,7 +173,7 @@ def test_trigger_lambda_model_training_executor_log_when_success(self): def __mock_make_api_call(self, operation_name, kwargs): assert kwargs == {'FunctionName': 'train-model', 'InvocationType': 'RequestResponse', 'LogType': 'Tail', - 'Payload': b'{"BOT":"test_bot","USER":"test_user"}'} + 'Payload': b'[{"name":"BOT","value":"test_bot"},{"name":"USER","value":"test_user"}]'} if operation_name == 'Invoke': return response @@ -182,16 +182,18 @@ def __mock_make_api_call(self, operation_name, kwargs): with patch.dict(Utility.environment, mock_env): with mock.patch('botocore.client.BaseClient._make_api_call', new=__mock_make_api_call): resp = CloudUtility.trigger_lambda(EventClass.model_training, - {"BOT": "test_bot", "USER": "test_user"}, + [{"name": "BOT", "value": "test_bot"}, + {"name": "USER", "value": "test_user"}], task_type=TASK_TYPE.EVENT.value) assert resp == response from kairon.shared.events.data_objects import ExecutorLogs - logs = ExecutorLogs.objects(task_type='Event', data={'BOT': 'test_bot', 'USER': 'test_user'}) + logs = ExecutorLogs.objects(task_type='Event', + data=[{"name": "BOT", "value": "test_bot"}, {"name": "USER", "value": "test_user"}]) log = logs[0].to_mongo().to_dict() assert log['task_type'] == 'Event' assert log['event_class'] == 'model_training' - assert log['data'] == {'BOT': 'test_bot', 'USER': 'test_user'} + assert log['data'] == [{"name": "BOT", "value": "test_bot"}, {"name": "USER", "value": "test_user"}] assert log['status'] == 'Completed' assert log['response'] == { 'StatusCode': 200, @@ -216,7 +218,7 @@ def test_trigger_lambda_model_training_executor_log_when_failed(self): def __mock_make_api_call(self, operation_name, kwargs): assert kwargs == {'FunctionName': 'train-model', 'InvocationType': 'RequestResponse', 'LogType': 'Tail', - 'Payload': b'{"BOT":"test_bot","USER":"test_user"}'} + 'Payload': b'[{"name":"BOT","value":"test_bot"},{"name":"USER","value":"test_user"}]'} raise Exception("Parameter validation failed: Invalid type for parameter FunctionName, value: None, " "type: , valid types: ") @@ -225,16 +227,18 @@ def __mock_make_api_call(self, operation_name, kwargs): with mock.patch('botocore.client.BaseClient._make_api_call', new=__mock_make_api_call): with pytest.raises(AppException): CloudUtility.trigger_lambda(EventClass.model_training, - {"BOT": "test_bot", "USER": "test_user"}, + [{"name": "BOT", "value": "test_bot"}, + {"name": "USER", "value": "test_user"}], task_type=TASK_TYPE.EVENT.value) from kairon.shared.events.data_objects import ExecutorLogs - logs = ExecutorLogs.objects(task_type='Event', data={'BOT': 'test_bot', 'USER': 'test_user'}) + logs = ExecutorLogs.objects(task_type='Event', + data=[{"name": "BOT", "value": "test_bot"}, {"name": "USER", "value": "test_user"}]) log = logs[1].to_mongo().to_dict() pytest.executor_log_id = log['executor_log_id'] assert log['task_type'] == 'Event' assert log['event_class'] == 'model_training' - assert log['data'] == {'BOT': 'test_bot', 'USER': 'test_user'} + assert log['data'] == [{"name": "BOT", "value": "test_bot"}, {"name": "USER", "value": "test_user"}] assert log['status'] == 'Fail' assert log['response'] == {} assert log['from_executor'] is False @@ -255,7 +259,7 @@ def test_trigger_lambda_model_training_executor_log_when_already_exist(self): def __mock_make_api_call(self, operation_name, kwargs): assert kwargs == {'FunctionName': 'train-model', 'InvocationType': 'RequestResponse', 'LogType': 'Tail', - 'Payload': b'{"BOT":"test_bot","USER":"test_user"}'} + 'Payload': b'[{"name":"BOT","value":"test_bot"},{"name":"USER","value":"test_user"}]'} if operation_name == 'Invoke': return response @@ -264,7 +268,8 @@ def __mock_make_api_call(self, operation_name, kwargs): with patch.dict(Utility.environment, mock_env): with mock.patch('botocore.client.BaseClient._make_api_call', new=__mock_make_api_call): resp = CloudUtility.trigger_lambda(EventClass.model_training, - {"BOT": "test_bot", "USER": "test_user"}, + [{"name": "BOT", "value": "test_bot"}, + {"name": "USER", "value": "test_user"}], task_type=TASK_TYPE.EVENT.value) assert resp == response @@ -272,7 +277,7 @@ def __mock_make_api_call(self, operation_name, kwargs): from kairon.events.executors.base import ExecutorBase executor = ExecutorBase() executor.log_task(event_class=EventClass.model_training.value, task_type=TASK_TYPE.EVENT.value, - data={'BOT': 'test_bot', 'USER': 'test_user'}, + data=[{"name": "BOT", "value": "test_bot"}, {"name": "USER", "value": "test_user"}], executor_log_id=pytest.executor_log_id, status=EVENT_STATUS.INITIATED.value) logs = ExecutorLogs.objects(executor_log_id=pytest.executor_log_id) @@ -280,7 +285,7 @@ def __mock_make_api_call(self, operation_name, kwargs): log = logs[1].to_mongo().to_dict() assert log['task_type'] == 'Event' assert log['event_class'] == 'model_training' - assert log['data'] == {'BOT': 'test_bot', 'USER': 'test_user'} + assert log['data'] == [{"name": "BOT", "value": "test_bot"}, {"name": "USER", "value": "test_user"}] assert log['status'] == 'Initiated' assert log['response'] == {} assert log['executor_log_id'] == pytest.executor_log_id @@ -298,7 +303,7 @@ def test_trigger_lambda_model_training(self): def __mock_make_api_call(self, operation_name, kwargs): assert kwargs == {'FunctionName': 'train-model', 'InvocationType': 'RequestResponse', 'LogType': 'Tail', - 'Payload': b'{"BOT":"test","USER":"test_user"}'} + 'Payload': b'[{"name":"BOT","value":"test"},{"name":"USER","value":"test_user"}]'} if operation_name == 'Invoke': return response @@ -306,7 +311,11 @@ def __mock_make_api_call(self, operation_name, kwargs): with patch.dict(Utility.environment, mock_env): with mock.patch('botocore.client.BaseClient._make_api_call', new=__mock_make_api_call): - resp = CloudUtility.trigger_lambda(EventClass.model_training, {"BOT": "test", "USER": "test_user"}) + resp = CloudUtility.trigger_lambda(EventClass.model_training, + [{"name": "BOT", "value": "test"}, + {"name": "USER", "value": "test_user"}], + task_type=TASK_TYPE.EVENT.value + ) assert resp == response def test_trigger_lambda_model_testing(self): @@ -322,7 +331,7 @@ def test_trigger_lambda_model_testing(self): def __mock_make_api_call(self, operation_name, kwargs): assert kwargs == {'FunctionName': 'test-model', 'InvocationType': 'RequestResponse', 'LogType': 'Tail', - 'Payload': b'{"BOT":"test","USER":"test_user"}'} + 'Payload': b'[{"name":"BOT","value":"test"},{"name":"USER","value":"test_user"}]'} if operation_name == 'Invoke': return response @@ -330,7 +339,11 @@ def __mock_make_api_call(self, operation_name, kwargs): with patch.dict(Utility.environment, mock_env): with mock.patch('botocore.client.BaseClient._make_api_call', new=__mock_make_api_call): - resp = CloudUtility.trigger_lambda(EventClass.model_testing, {"BOT": "test", "USER": "test_user"}) + resp = CloudUtility.trigger_lambda(EventClass.model_testing, + [{"name": "BOT", "value": "test"}, + {"name": "USER", "value": "test_user"}], + task_type=TASK_TYPE.EVENT.value + ) assert resp == response def test_trigger_lambda_data_importer(self): @@ -346,7 +359,7 @@ def test_trigger_lambda_data_importer(self): def __mock_make_api_call(self, operation_name, kwargs): assert kwargs == {'FunctionName': 'data-importer', 'InvocationType': 'RequestResponse', 'LogType': 'Tail', - 'Payload': b'{"BOT":"test","USER":"test_user"}'} + 'Payload': b'[{"name":"BOT","value":"test"},{"name":"USER","value":"test_user"}]'} if operation_name == 'Invoke': return response @@ -354,7 +367,10 @@ def __mock_make_api_call(self, operation_name, kwargs): with patch.dict(Utility.environment, mock_env): with mock.patch('botocore.client.BaseClient._make_api_call', new=__mock_make_api_call): - resp = CloudUtility.trigger_lambda(EventClass.data_importer, {"BOT": "test", "USER": "test_user"}) + resp = CloudUtility.trigger_lambda(EventClass.data_importer, + [{"name": "BOT", "value": "test"}, + {"name": "USER", "value": "test_user"}], + task_type=TASK_TYPE.EVENT.value) assert resp == response def test_trigger_lambda_public_search(self): @@ -395,7 +411,7 @@ def test_trigger_lambda_delete_history(self): def __mock_make_api_call(self, operation_name, kwargs): assert kwargs == {'FunctionName': 'delete-history', 'InvocationType': 'RequestResponse', 'LogType': 'Tail', - 'Payload': b'{"BOT":"test","USER":"test_user"}'} + 'Payload': b'[{"name":"BOT","value":"test"},{"name":"USER","value":"test_user"}]'} if operation_name == 'Invoke': return response @@ -403,5 +419,8 @@ def __mock_make_api_call(self, operation_name, kwargs): with patch.dict(Utility.environment, mock_env): with mock.patch('botocore.client.BaseClient._make_api_call', new=__mock_make_api_call): - resp = CloudUtility.trigger_lambda(EventClass.delete_history, {"BOT": "test", "USER": "test_user"}) + resp = CloudUtility.trigger_lambda(EventClass.delete_history, + [{"name": "BOT", "value": "test"}, + {"name": "USER", "value": "test_user"}], + task_type=TASK_TYPE.EVENT.value) assert resp == response diff --git a/tests/unit_test/data_processor/executor_processor_test.py b/tests/unit_test/data_processor/executor_processor_test.py new file mode 100644 index 000000000..1e3011dcc --- /dev/null +++ b/tests/unit_test/data_processor/executor_processor_test.py @@ -0,0 +1,393 @@ +import os + +import pytest +from bson import ObjectId +from mongoengine import connect + +from kairon.shared.constants import EventClass +from kairon.shared.data.constant import EVENT_STATUS, TASK_TYPE +from kairon.shared.events.processor import ExecutorProcessor +from kairon.shared.utils import Utility + +os.environ["system_file"] = "./tests/testing_data/system.yaml" +Utility.load_environment() +Utility.load_system_metadata() + + +class TestExecutorProcessor: + + @pytest.fixture(autouse=True, scope='class') + def init_connection(self): + connect(**Utility.mongoengine_connection()) + + @pytest.fixture() + def get_executor_logs(self): + from kairon.events.executors.base import ExecutorBase + executor = ExecutorBase() + executor.log_task(event_class=EventClass.model_training.value, task_type=TASK_TYPE.EVENT.value, + data=[{"name": "BOT", "value": '66cd84e4f206edf5b776d6d8'}, + {"name": "USER", "value": "test_user"}], + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.INITIATED.value, + from_executor=True) + executor.log_task(event_class=EventClass.model_testing.value, task_type=TASK_TYPE.EVENT.value, + data=[{"name": "BOT", "value": '66cd84e4f206edf5b776d6d8'}, + {"name": "USER", "value": "test_user"}], + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.INITIATED.value, + from_executor=True) + executor.log_task(event_class=EventClass.web_search.value, task_type=TASK_TYPE.ACTION.value, + data={"text": "I am good", "site": "www.kairon.com", "topn": 1, + "bot": "66cd84e4f206edf5b776d6d8"}, + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.COMPLETED.value, + response={ + "ResponseMetadata": { + "RequestId": "dc824d8c-843b-47fe-8b56-fd6cbec3023403b", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "date": "Wed, 18 Sep 2024 09:15:07 GMT", + "content-type": "application/json", + "content-length": "506", + "connection": "keep-alive", + "x-amzn-requestid": "dc824d8c-843b-47fe-8b56-fd6cbec3025b", + "x-amzn-remapped-content-length": "0", + "x-amz-executed-version": "$LATEST", + "x-amz-log-result": "dfldfjklkdlsfkldfdjdjfdkj", + "x-amzn-trace-id": "Root=1-66ea9a18-7dc80b6e0c0873b139369c35;Sampled=1;Lineage=1:79a5f83d:0" + }, + "RetryAttempts": 0 + }, + "StatusCode": 200, + "LogResult": "kdflkdflkdkfldkfldklfdklfkdljdgn", + "ExecutedVersion": "$LATEST", + "Payload": { + "statusCode": 200, + "statusDescription": "200 OK", + "isBase64Encoded": False, + "headers": { + "Content-Type": "text/html; charset=utf-8" + }, + "body": [ + { + "title": "David Guetta & Bebe Rexha - I'm Good (Blue) [Official Music Video] - YouTube", + "description": "Listen to "I'm Good (Blue)" by David Guetta and Bebe Rexha: https://davidguetta.lnk.to/ImGood🔔 Subscribe to be notified for new videoshttps://davidguetta.ln", + "url": "https://www.youtube.com/watch?v=90RLzVUuXe4" + } + ] + } + } + ) + executor.log_task(event_class=EventClass.scheduler_evaluator.value, task_type=TASK_TYPE.ACTION.value, + data=[ + { + "name": "SOURCE_CODE", + "value": "import requests\n\nurl=\"https://waba-v2.360dialog.io/messages\"\n\nheaders={'Content-Type': 'application/json', 'D360-API-KEY' : 'mks3hj3489348938493849839R3RAK'}\n\ncontacts = ['919657099999','918210099999']\ncontacts = [\"919515999999\"]\nbody = {\n \"messaging_product\": \"whatsapp\",\n \"recipient_type\": \"individual\",\n \"to\": \"9199991685\",\n \"type\": \"template\",\n \"template\": {\n \"namespace\": \"54500467_f322_4595_becd_41555889bfd8\",\n \"language\": {\n \"policy\": \"deterministic\",\n \"code\": \"en\"\n },\n \"name\": \"schedule_action_test\"\n }\n}\n\nfor contact in contacts:\n body[\"to\"] = contact\n resp = requests.post(url, headers=headers, data=json.dumps(body))\n resp = resp.json()\n print(resp[\"messages\"])\n\nbot_response = 'this from callback pyscript'" + }, + { + "name": "PREDEFINED_OBJECTS", + "value": { + "val1": "rajan", + "val2": "hitesh", + "passwd": None, + "myuser": "mahesh.sattala@digite.com", + "bot": '66cd84e4f206edf5b776d6d8', + "event": "01928ec7f495717b842dfdjfkdc65a161e3" + } + } + ], + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.FAIL.value, + exception="An error occurred (UnrecognizedClientException) when calling the Invoke operation: The security token included in the request is invalid.") + executor.log_task(event_class=EventClass.pyscript_evaluator.value, task_type=TASK_TYPE.ACTION.value, + data={ + "source_code": "bot_response = \"Test\"", + "predefined_objects": { + "sender_id": "mahesh.sattala@digite.com", + "user_message": "/pyscript", + "slot": { + "calbkslot1": None, + "calbkslot2": None, + "quick_reply": None, + "latitude": None, + "longitude": None, + "doc_url": None, + "document": None, + "video": None, + "audio": None, + "image": None, + "http_status_code": None, + "flow_reply": None, + "order": None, + "bot": '66cd84e4f206edf5b776d6d8', + "kairon_action_response": "Test", + "session_started_metadata": { + "tabname": "default", + "displayLabel": "", + "telemetry-uid": "None", + "telemetry-sid": "None", + "is_integration_user": False, + "bot": "66cd84e4f206edf5b776d6d8", + "account": 8, + "channel_type": "chat_client" + } + }, + "intent": "pyscript", + "chat_log": [ + { + "user": "hi" + }, + { + "bot": { + "text": "Let me be your AI Assistant and provide you with service", + "elements": None, + "quick_replies": None, + "buttons": None, + "attachment": None, + "image": None, + "custom": None + } + }, + { + "user": "trigger callback" + }, + { + "bot": { + "text": "callbk triggered", + "elements": None, + "quick_replies": None, + "buttons": None, + "attachment": None, + "image": None, + "custom": None + } + }, + { + "user": "/pyscript" + } + ], + "key_vault": {}, + "latest_message": { + "intent": { + "name": "pyscript", + "confidence": 1 + }, + "entities": [], + "text": "/pyscript", + "message_id": "ee57dde3c007448eb257d6b5e20c30ac", + "metadata": { + "tabname": "default", + "displayLabel": "", + "telemetry-uid": "None", + "telemetry-sid": "None", + "is_integration_user": False, + "bot": '66cd84e4f206edf5b776d6d8', + "account": 8, + "channel_type": "chat_client" + }, + "intent_ranking": [ + { + "name": "pyscript", + "confidence": 1 + } + ] + }, + "kairon_user_msg": None, + "session_started": None + } + }, + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.INITIATED.value) + executor.log_task(event_class=EventClass.pyscript_evaluator.value, task_type=TASK_TYPE.CALLBACK.value, + data={ + "source_code": "bot_response = \"test - this is from callback test\"", + "predefined_objects": { + "req": { + "type": "GET", + "body": None, + "params": {} + }, + "req_host": "127.0.0.1", + "action_name": "clbk1", + "callback_name": "test", + "bot": '66cd84e4f206edf5b776d6d8', + "sender_id": "mahesh.sattala@digite.com", + "channel": "unsupported (None)", + "metadata": { + "first_name": "rajan", + "bot": '66cd84e4f206edf5b776d6d8' + }, + "identifier": "01928eb52813799e81c56803ecf39e6e", + "callback_url": "http://localhost:5059/callback/d/01928eb52813799e81c56803ecf39e6e/98bxWAMY9HF40La5AKQjEb-0JqaDTKX_Mmmmmmmmmmm", + "execution_mode": "async", + "state": 0, + "is_valid": True + } + }, + executor_log_id=ObjectId().__str__(), status=EVENT_STATUS.COMPLETED.value, + response={ + "ResponseMetadata": { + "RequestId": "0da1cdd1-1702-473b-8109-67a39f990835", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "date": "Tue, 15 Oct 2024 07:38:01 GMT", + "content-type": "application/json", + "content-length": "740", + "connection": "keep-alive", + "x-amzn-requestid": "0da1cdd1-1702-473b-8109-67a39f9sdlksldksl", + "x-amzn-remapped-content-length": "0", + "x-amz-executed-version": "$LATEST", + "x-amz-log-result": "sdskjdksjdkjskdjskjdksj", + "x-amzn-trace-id": "Root=1-670e1bd6-25e7667c7c6ce37f09a9afc7;Sampled=1;Lineage=1:d072fc6c:0" + }, + "RetryAttempts": 0 + }, + "StatusCode": 200, + "LogResult": "sldklskdlskdlskdlk", + "ExecutedVersion": "$LATEST", + "Payload": { + "statusCode": 200, + "statusDescription": "200 OK", + "isBase64Encoded": False, + "headers": { + "Content-Type": "text/html; charset=utf-8" + }, + "body": { + "req": { + "type": "GET", + "body": None, + "params": {} + }, + "req_host": "127.0.0.1", + "action_name": "clbk1", + "callback_name": "test", + "bot": '66cd84e4f206edf5b776d6d8', + "sender_id": "mahesh.sattala@digite.com", + "channel": "unsupported (None)", + "metadata": { + "first_name": "rajan", + "bot": '66cd84e4f206edf5b776d6d8' + }, + "identifier": "01928eb52813799e81c56803ecf39e6e", + "callback_url": "http://localhost:5059/callback/d/01928eb52813799e81c56803ecf39e6e/98bxWAMY9HF40La5AKQjEb-0JqaDTKX_Mmmmmmmmmmm", + "execution_mode": "async", + "state": 0, + "is_valid": True, + "bot_response": "test - this is from callback test" + } + } + } + ) + + def test_get_executor_logs(self, get_executor_logs): + processor = ExecutorProcessor() + logs = list(processor.get_executor_logs("66cd84e4f206edf5b776d6d8", task_type="Callback")) + assert len(logs) == 1 + assert logs[0]["task_type"] == "Callback" + assert logs[0]["event_class"] == "pyscript_evaluator" + assert logs[0]["status"] == "Completed" + assert logs[0]["data"] == { + 'source_code': 'bot_response = "test - this is from callback test"', + 'predefined_objects': { + 'req': {'type': 'GET', 'body': None, 'params': {}}, + 'req_host': '127.0.0.1', 'action_name': 'clbk1', + 'callback_name': 'test', 'bot': '66cd84e4f206edf5b776d6d8', + 'sender_id': 'mahesh.sattala@digite.com', + 'channel': 'unsupported (None)', + 'metadata': {'first_name': 'rajan', + 'bot': '66cd84e4f206edf5b776d6d8'}, + 'identifier': '01928eb52813799e81c56803ecf39e6e', + 'callback_url': 'http://localhost:5059/callback/d/01928eb52813799e81c56803ecf39e6e/98bxWAMY9HF40La5AKQjEb-0JqaDTKX_Mmmmmmmmmmm', + 'execution_mode': 'async', 'state': 0, 'is_valid': True} + } + assert logs[0]["response"] == { + 'ResponseMetadata': {'RequestId': '0da1cdd1-1702-473b-8109-67a39f990835', 'HTTPStatusCode': 200, + 'HTTPHeaders': {'date': 'Tue, 15 Oct 2024 07:38:01 GMT', + 'content-type': 'application/json', 'content-length': '740', + 'connection': 'keep-alive', + 'x-amzn-requestid': '0da1cdd1-1702-473b-8109-67a39f9sdlksldksl', + 'x-amzn-remapped-content-length': '0', + 'x-amz-executed-version': '$LATEST', + 'x-amz-log-result': 'sdskjdksjdkjskdjskjdksj', + 'x-amzn-trace-id': 'Root=1-670e1bd6-25e7667c7c6ce37f09a9afc7;Sampled=1;Lineage=1:d072fc6c:0'}, + 'RetryAttempts': 0}, 'StatusCode': 200, 'LogResult': 'sldklskdlskdlskdlk', + 'ExecutedVersion': '$LATEST', + 'Payload': {'statusCode': 200, 'statusDescription': '200 OK', 'isBase64Encoded': False, + 'headers': {'Content-Type': 'text/html; charset=utf-8'}, + 'body': {'req': {'type': 'GET', 'body': None, 'params': {}}, 'req_host': '127.0.0.1', + 'action_name': 'clbk1', 'callback_name': 'test', 'bot': '66cd84e4f206edf5b776d6d8', + 'sender_id': 'mahesh.sattala@digite.com', 'channel': 'unsupported (None)', + 'metadata': {'first_name': 'rajan', 'bot': '66cd84e4f206edf5b776d6d8'}, + 'identifier': '01928eb52813799e81c56803ecf39e6e', + 'callback_url': 'http://localhost:5059/callback/d/01928eb52813799e81c56803ecf39e6e/98bxWAMY9HF40La5AKQjEb-0JqaDTKX_Mmmmmmmmmmm', + 'execution_mode': 'async', 'state': 0, 'is_valid': True, + 'bot_response': 'test - this is from callback test'}}} + assert logs[0]["executor_log_id"] + assert logs[0]['bot'] == "66cd84e4f206edf5b776d6d8" + + logs = list(processor.get_executor_logs("66cd84e4f206edf5b776d6d8", task_type="Event")) + assert len(logs) == 2 + assert logs[0]["task_type"] == "Event" + assert logs[0]["event_class"] == "model_testing" + assert logs[0]["status"] == "Initiated" + assert logs[0]["data"] == [ + { + 'name': 'BOT', + 'value': "66cd84e4f206edf5b776d6d8" + }, + { + 'name': 'USER', + 'value': 'test_user' + } + ] + assert logs[0]["executor_log_id"] + assert logs[0]['from_executor'] is True + assert logs[0]['bot'] == "66cd84e4f206edf5b776d6d8" + + assert logs[1]["task_type"] == "Event" + assert logs[1]["event_class"] == "model_training" + assert logs[1]["status"] == "Initiated" + assert logs[1]["data"] == [ + { + 'name': 'BOT', + 'value': "66cd84e4f206edf5b776d6d8" + }, + { + 'name': 'USER', + 'value': 'test_user' + } + ] + assert logs[1]["executor_log_id"] + assert logs[1]['from_executor'] is True + assert logs[1]['bot'] == "66cd84e4f206edf5b776d6d8" + + logs = list(processor.get_executor_logs("66cd84e4f206edf5b776d6d8", task_type="Event", + event_class="model_training")) + assert len(logs) == 1 + assert logs[0]["task_type"] == "Event" + assert logs[0]["event_class"] == "model_training" + assert logs[0]["status"] == "Initiated" + assert logs[0]["data"] == [ + { + 'name': 'BOT', + 'value': "66cd84e4f206edf5b776d6d8" + }, + { + 'name': 'USER', + 'value': 'test_user' + } + ] + assert logs[0]["executor_log_id"] + assert logs[0]['from_executor'] is True + assert logs[0]['bot'] == "66cd84e4f206edf5b776d6d8" + + @pytest.mark.parametrize("event_class, task_type, expected_count", [ + (None, None, 6), + ("pyscript_evaluator", None, 2), + ("pyscript_evaluator", "Callback", 1), + (None, "Event", 2), + ("model_testing", "Event", 1), + (None, "Action", 3), + ("web_search", "Action", 1), + ]) + def test_get_row_count(self, event_class, task_type, expected_count): + processor = ExecutorProcessor() + count = processor.get_row_count("66cd84e4f206edf5b776d6d8", event_class=event_class, task_type=task_type) + assert count == expected_count + +