Skip to content

Commit

Permalink
http action - time_elapsed, pyscript for dynamic body and response sc…
Browse files Browse the repository at this point in the history
…ript (#1124)

* added response_time in http_action logs

* fixed typo in the log statement for http_action response_time

* changed http action dynamic params and response body to pyscript, added latest_messages in the context added and fixed corresponding test cases

* test case fixes

* http action - time_elapsed, pyscript for dynamic body and response script

---------

Co-authored-by: spandan.mondal <[email protected]>
  • Loading branch information
hasinaxp and spandan.mondal authored Jan 25, 2024
1 parent 91775c1 commit 706e6e8
Show file tree
Hide file tree
Showing 8 changed files with 774 additions and 964 deletions.
18 changes: 12 additions & 6 deletions kairon/actions/definitions/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ async def execute(self, dispatcher: CollectingDispatcher, tracker: Tracker, doma
dispatch_bot_response = True
dispatch_type = DispatchType.text.value
msg_logger = []
time_elapsed = None
response_status_code = None
try:
http_action_config = self.retrieve_config()
dispatch_bot_response = http_action_config['response']['dispatch']
Expand All @@ -74,19 +76,20 @@ async def execute(self, dispatcher: CollectingDispatcher, tracker: Tracker, doma
logger.info("headers: " + str(header_log))
dynamic_params = http_action_config.get('dynamic_params')
if not ActionUtility.is_empty(dynamic_params):
body, body_log = ActionUtility.evaluate_script(dynamic_params, tracker_data)
body, body_log, slots = ActionUtility.evaluate_pyscript(dynamic_params, tracker_data)
filled_slots.update(slots)
msg_logger.extend(body_log)
body_log = ActionUtility.encrypt_secrets(body, tracker_data)
else:
body, body_log = ActionUtility.prepare_request(tracker_data, http_action_config['params_list'], self.bot)
logger.info("request_body: " + str(body_log))
request_method = http_action_config['request_method']
http_url = ActionUtility.prepare_url(http_url=http_action_config['http_url'], tracker_data=tracker_data)
http_response = await ActionUtility.execute_request_async(headers=headers, http_url=http_url,
http_response, response_status_code, time_elapsed = await ActionUtility.execute_request_async(headers=headers, http_url=http_url,
request_method=request_method, request_body=body,
content_type=http_action_config['content_type'])
logger.info("http response: " + str(http_response))
response_context = self.__add_user_context_to_http_response(http_response, tracker_data)
response_context = self.__add_user_context_to_http_response(http_response, response_status_code, tracker_data)
bot_response, bot_resp_log = ActionUtility.compose_response(http_action_config['response'],
response_context)
msg_logger.extend(bot_resp_log)
Expand All @@ -107,6 +110,7 @@ async def execute(self, dispatcher: CollectingDispatcher, tracker: Tracker, doma
bot_response, message = ActionUtility.handle_utter_bot_response(dispatcher, dispatch_type, bot_response)
if message:
msg_logger.append(message)
fail_reason = ActionUtility.validate_http_response_status(http_response, response_status_code)
ActionServerLogs(
type=ActionType.http_action.value,
intent=tracker.get_intent_of_latest_message(skip_fallback_intent=False),
Expand All @@ -119,17 +123,19 @@ async def execute(self, dispatcher: CollectingDispatcher, tracker: Tracker, doma
api_response=str(http_response) if http_response else None,
bot_response=str(bot_response) if bot_response else None,
messages=msg_logger,
fail_reason=fail_reason,
exception=exception,
bot=self.bot,
status=status,
user_msg=tracker.latest_message.get('text')
user_msg=tracker.latest_message.get('text'),
time_elapsed=time_elapsed
).save()
filled_slots.update({KaironSystemSlots.kairon_action_response.value: bot_response})
return filled_slots

@staticmethod
def __add_user_context_to_http_response(http_response, tracker_data):
response_context = {"data": http_response, 'context': tracker_data}
def __add_user_context_to_http_response(http_response, response_status_code, tracker_data):
response_context = {"data": http_response, 'context': tracker_data, 'status_code': response_status_code}
return response_context

@property
Expand Down
1 change: 1 addition & 0 deletions kairon/shared/actions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ActionParameterType(str, Enum):
slot = "slot"
sender_id = "sender_id"
user_message = "user_message"
latest_message = "latest_message"
intent = "intent"
chat_log = "chat_log"
key_vault = "key_vault"
Expand Down
33 changes: 29 additions & 4 deletions kairon/shared/actions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,20 @@ async def execute_request_async(http_url: str, request_method: str, request_body
kwargs = {"content_type": content_type, "timeout": timeout, "return_json": False}
client = AioRestClient()
response = await client.request(request_method, http_url, request_body, headers, **kwargs)
if response.status not in [200, 202, 201, 204]:
raise ActionFailure(f"Got non-200 status code: {response.status_code} {response.text}")

try:
http_response = await response.json()
except (ContentTypeError, ValueError) as e:
logging.error(str(e))
http_response = await response.text()
status_code = response.status

return http_response, status_code, client.time_elapsed

return http_response
@staticmethod
def validate_http_response_status(http_response, status_code):
if status_code and status_code not in [200, 202, 201, 204]:
return f"Got non-200 status code: {status_code} {http_response}"

@staticmethod
def execute_http_request(http_url: str, request_method: str, request_body=None, headers=None,
Expand Down Expand Up @@ -250,6 +255,7 @@ def build_context(tracker: Tracker, extract_keyvault: bool = False):
ActionParameterType.intent.value: tracker.get_intent_of_latest_message(),
ActionParameterType.chat_log.value: msg_trail,
ActionParameterType.key_vault.value: key_vault,
ActionParameterType.latest_message.value : tracker.latest_message,
KAIRON_USER_MSG_ENTITY: next(tracker.get_latest_entity_values(KAIRON_USER_MSG_ENTITY), None),
"session_started": iat
}
Expand Down Expand Up @@ -635,7 +641,7 @@ def compose_response(response_config: dict, http_response: Any):
f"Skipping evaluation as value is empty"
])
elif evaluation_type == EvaluationType.script.value:
result, log = ActionUtility.evaluate_script(response, http_response)
result, log, _ = ActionUtility.evaluate_pyscript(response, http_response)
else:
result = ActionUtility.prepare_response(response, http_response)
log.extend([f"evaluation_type: {evaluation_type}", f"expression: {response}", f"data: {http_response}",
Expand Down Expand Up @@ -919,6 +925,25 @@ def evaluate_script(script: Text, data: Any, raise_err_on_failure: bool = True):
result = resp.get('data')
return result, log

@staticmethod
def evaluate_pyscript(script: Text, data: Any, raise_err_on_failure: bool = True):
log = [f"evaluation_type: script", f"script: {script}", f"data: {data}", f"raise_err_on_failure: {raise_err_on_failure}"]
if data.get('context'):
context = data['context']
context['data'] = data['data']
context['status_code'] = data['status_code']
else:
context = data
result = ActionUtility.run_pyscript(script, context)
slot_values = ActionUtility.filter_out_kairon_system_slots(result.get('slots', {}))
if result.get('bot_response'):
output = result['bot_response']
elif result.get('body'):
output = result['body']
else:
output = {}
return output, log, slot_values

@staticmethod
def trigger_rephrase(bot: Text, text_response: Text):
rephrased_message = None
Expand Down
12 changes: 12 additions & 0 deletions kairon/shared/rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from kairon import Utility
from kairon.exceptions import AppException
from kairon.shared.actions.models import HttpRequestContentType
from datetime import datetime


class RestClientBase(ABC):
Expand All @@ -28,6 +29,7 @@ def __init__(self, close_session_with_rqst_completion=True):
self.session = ClientSession()
self.close_session_with_rqst_completion = close_session_with_rqst_completion
self._streaming_response = None
self._time_elapsed = None

@property
def streaming_response(self):
Expand Down Expand Up @@ -57,6 +59,14 @@ async def request(self, request_method: str, http_url: str, request_body: Union[

return response

@property
def time_elapsed(self):
return self._time_elapsed

@time_elapsed.setter
def time_elapsed(self, time_elapsed):
self._time_elapsed = time_elapsed.microseconds / 1000

async def __trigger_request(self, request_method: str, http_url: str, retry_options: ExponentialRetry,
request_body: Union[dict, list] = None, headers: dict = None,
content_type: str = HttpRequestContentType.json.value,
Expand Down Expand Up @@ -96,7 +106,9 @@ async def __trigger(self, client, *args, **kwargs) -> ClientResponse:
Response object is returned as it is and Streaming response is set into class property.
"""
is_streaming_resp = kwargs.pop("is_streaming_resp", False)
rqst_start_time = datetime.utcnow()
async with client.request(*args, **kwargs) as response:
self.time_elapsed = datetime.utcnow() - rqst_start_time
logger.debug(f"Content-type: {response.headers['content-type']}")
logger.debug(f"Status code: {str(response.status)}")
if is_streaming_resp:
Expand Down
2 changes: 1 addition & 1 deletion system.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ evaluator:
url: ${EXPRESSION_EVALUATOR_ENDPOINT:"http://192.168.100.109:8085/evaluate"}
pyscript:
trigger_task: ${PYSCRIPT_TRIGGER_TASK:false}
url: ${PYSCRIPT_EVALUATOR_ENDPOINT:"http://192.168.100.109:8080/evaluate"}
url: ${PYSCRIPT_EVALUATOR_ENDPOINT:"http://192.168.100.109:8087/evaluate"}

multilingual:
enable: ${ENABLE_MULTILINGUAL_BOTS:false}
Expand Down
Loading

0 comments on commit 706e6e8

Please sign in to comment.