Name

global.AutoResolutionLanguageXStage

Description

No description available

Script

var AutoResolutionLanguageXStage = Class.create();
AutoResolutionLanguageXStage.prototype = Object.extendsObject(AutoResolutionTaskProcessingStage, {

  getStateValue: function() {
  	return AutoResolutionConstants.TASK_PROCESSING_STATE.LANGUAGEX;
  },

  execute: function(contextFieldValueMap) {
  	this.prepare(contextFieldValueMap, this.type);
  	
  	if(!this._shouldStageContinue())
  		return this.response;

  	/**
  	 *  We are making the assumption that if ML prediction call can't be made, we are exiting the IAR flow
  	 *  ITSM will also be uptaking languageX API's starting Utah
  	 */
  	var extResponse = this.processPreProcessingExtension();
  	if (gs.nil(extResponse)) {
  		this.setError("Pre-processing extension execution returned a status error");
  		return this.response;
  	}
  	var inputFields = this._getPredictionInputFields(extResponse);
  	var options = this._getPredictionOptions(extResponse);

  	if (gs.nil(inputFields)) {
  		this.LOGGER.warn("Input fields for prediction are empty, Composite services will not be called for task number={0}", this.taskGr.getValue('number'));
  		this.setError("Input fields for prediction are empty, Composite services will not be called");
  		return this.response;
  	}

  	this.LOGGER.info("Initiating Composite service call with prediction fields={0} for the task number={1}",
  		JSON.stringify(inputFields), this.taskGr.getValue('number'));

  	// Create predictions using LanguageX
  	var predictionSysId;
  	var contextReason = '';
  	try {
  		var languageXResult = this.mlHelper.predict(this.taskGr, inputFields, options, this.languageCode);
  		predictionSysId = gs.nil(languageXResult.prediction_id) ? '' : languageXResult.prediction_id;

  		if (languageXResult.status == AutoResolutionConstants.STATUS_ERROR) {
  			this.LOGGER.info('An error occurred. message: {0}', languageXResult.message); 
  			contextReason = AutoResolutionConstants.NO_INTENT_REASON['API_ERROR'];
  		} else {
  			this.LOGGER.info("Successfully saved Composite prediction response in service results tables");
  			this.processPostProcessExtension(languageXResult);
  		}

  	} catch (error) {
  		this.LOGGER.error('Error calling Composite predictions for task {0}. Error: {1}', this.taskGr.getValue('number'), error);
  		this.setError(error);
  	}
  	
  	this.setContextReason(contextReason);
  	this.setContextValue('prediction', predictionSysId);
  	return this.response;
  },

  processPreProcessingExtension: function() {
  	var extResponse;

  	// Try to get the response from the pre-processing extension
  	try {
  		extResponse = this._executePreProcessExtension();
  	} catch(error) {
  		this.LOGGER.warn(
  			'An error occurred while executing pre-process extension for config_sys_id:{0}, task_table:{1}, task_sys_id:{2} error:{3}',
  			this.configId, this.taskGr.getTableName(), this.taskId, error);
  		return null;
  	}

  	// No need to process
  	if (gs.nil(extResponse))
  		return null;

  	// No need to process when the pre-processing extension returns a status Error as well
  	if (extResponse.status === global.AutoResolutionConstants.STATUS_ERROR) {
  		this.logger.warn('Pre-processing extension execution returned a status error, reason={0}', extResponse.message);
  		return null;
  	}

  	return extResponse;
  },
  
  /**
   * Performs initial checks on whether the LanguageX stage is fully supported to determine whether the 
   * stage should continue to execute.
   * @return {boolean} whether to continue the LanguageX stage
   */
  _shouldStageContinue: function() {
  	// The LanguageX stage should not execute if there is no configuration language 
  	// record for supported ML capabilities
  	if (!AutoResolutionLanguageXHelper.shouldLanguageXExecute(this.configId)) {
  		this.setContextReason(AutoResolutionConstants.NO_INTENT_REASON['INTENT_PREDICTION_NOT_ENABLED']);
  		return false;
  	}

  	// Get the ML solution object to check if LanguageX can execute given the capability type
  	var capability;
  	try {
  		// Dynamically decide whether to call LanguageX or Composite predict
  		this.mlHelper = AutoResolutionMLFactory.createMLHelper(this.configId, this.contextId, true);
  		capability = this.mlHelper.getCapability();

  		// Check if task language is supported for the ML capability used
  		if (!AutoResolutionLanguageXHelper.canLanguageXExecute(this.configId, this.languageCode, capability)) {
  			this.setContextReason(AutoResolutionConstants.NO_INTENT_REASON['INACTIVE_LANGUAGE']);
  			return false;
  		}
  	} catch (error) {
  		this.LOGGER.error('Error fetching the ML solution object for task {0} with the capability: {1}. Error: {2}', 
  			this.taskGr.getValue('number'), capability, error);
  		this.setError(error);
  		return false;
  	}

  	return true;
  },

  /**
   * Returns the prediction input fields for LanguageX call.
   */
  _getPredictionInputFields: function(extResponse) {

  	if (gs.nil(extResponse.input_field) || Object.keys(extResponse.input_field).length == 0) {
  		this.LOGGER.warn(
  			'Input field(s) are empty for config_sys_id:{0}, task_table:{1}, task_sys_id:{2} extension result:{3}',
  			this.configId, this.taskGr.getTableName(), this.taskId, JSON.stringify(extResponse));
  		return null;
  	}

  	// if found, return the result.
  	var predictionInputs = extResponse.input_field;
  	this.LOGGER.info("Using prediction inputs={0} from the pre-process response", JSON.stringify(predictionInputs));

  	return predictionInputs;
  },

  /**
   *
   * @param extResponse
   * @private
   */
  _getPredictionOptions: function(extResponse) {
  	if (gs.nil(extResponse.options) || Object.keys(extResponse.options).length == 0)
  		return {};

  	var options = extResponse.options;
  	this.LOGGER.debug("Using prediction options={0) from pre-process response", JSON.stringify(options));
  	return options;
  },

  /**
   * Executes pre-processing extension point if found.
   * @param configGr
   * @param taskGr
   * @return null if no pre-processing script is not found or error happened
   */
  _executePreProcessExtension: function() {

  	// Get the sysId of the script to execute
  	var scriptSysId = this._getExtensionScriptSysId(AutoResolutionConstants.PRE_PROCESSING_EXT_POINT_FIELD_NAME);

  	// If null, the extension is not set
  	if (gs.nil(scriptSysId)) {
  		this.LOGGER.info("Auto-Resolution Configuration doesn't have pre-processing extension associated with it.");
  		return null;
  	}

  	// get the parameter bag
  	var paramBag = this._getParamBagForPreProcessExtension();
  	return this.executeExtensionScript(scriptSysId, paramBag);
  },

  /**
   * Processes post-process extension after LanguageX invocation
   *
   * @param configGr: the config_getParamBagForPostProcessExtension record. null is not allowed
   * @param languageXResult: the result from LanguageX. Null is not allowed.
   */
  processPostProcessExtension: function(languageXResult) {

  	if (gs.nil(languageXResult)) {
  		this.LOGGER.warn('the result from LanguageX is null. Stopped processing post-process extension');
  		return;
  	}

  	//Execute the post processing extension point.
  	var extResponse;

  	try {
  		extResponse = this._executePostProcessingExtension(languageXResult.payload);
  	} catch(e) {
  		this.LOGGER.warn("An error occurred while executing post-process extension:{0}, payload:{1}",
  			configGr.post_processing_extension.script_include.name, JSON.stringify(languageXResult.payload));

  		return;
  	}

  	//post-process extension is not registered or inactive.
  	if (gs.nil(extResponse))
  		return;

  	if(gs.nil(extResponse.status == AutoResolutionConstants.STATUS_ERROR)) {
  		this.LOGGER.warn("An error occurred while executing post-process extension:{0}, message:{1}, payload:{1}",
  			configGr.post_processing_extension.script_include.name, extResponse.message, JSON.stringify(languageXResult.payload));

  		return;
  	}

  	// Update the prediction record with the result from the extension point
  	var predictionGr = new GlideRecord(AutoResolutionConstants.PREDICTION_TABLE_NAME);

  	if (!predictionGr.get(languageXResult.prediction_id)) {
  		this.LOGGER.warn("Could not find the prediction record with the sys_id:{0}, Composite result:{1}",
  			languageXResult.prediction_id, JSON.stringify(languageXResult.payload));
  		return;
  	}
  	
  	this._updatePredictionResults(extResponse, languageXResult);
  },
  
  /**
  * Update prediction table from the languageX result and post extension 
  */
  _updatePredictionResults :function(extResponse, languageXResult) {
  	
  	if (gs.nil(extResponse.result) || extResponse.result.length == 0)
  		return;
  	
  	var payload = languageXResult.payload;
  	
  	var fieldMap = extResponse.result[0]; // note that currenlty, only the first array is supported.
  			
  	if (gs.nil(fieldMap['predicted_language'])) {
  		// if the output of the extension point does not contain the predicted language, then get the detected language from the payload
  		
  		// find the array of outputs with languageDetection from the languageX result.
  		var langOutputs 
  			= AutoResolutionLanguageXHelper.getOutputsByServiceName(
  				payload, AutoResolutionConstants.LANGUAGE_DETECTION_SERVICE_NAME, this.LOGGER);
  	
  		// update the detected language
  		var langOutput = langOutputs[0]; // note that currently, only the first array is supported.
  		fieldMap['predicted_language'] = gs.nil(langOutput.serviceOutput)? '' : langOutput.serviceOutput;  
  	}

  	// if the output of the extension point does not contain the search queries,
  	// then pass the values from prediction fields set on the IAR Configuration
  	if (gs.nil(fieldMap['predicted_search_query'])) {

  		// Get the field names from the passed task table. These fields will be used for the LanguageX input
  		var taskFieldNames = global.AutoResolutionUtil.getTaskTableFieldNamesForARPredictionInput(this.taskGr.getTableName());

  		var searchInput = "";

  		for (var i=0; i<taskFieldNames.length; i++) {
  			fieldName = taskFieldNames[i];
  			fieldValue = this.taskGr.getValue(fieldName);

  			if (!gs.nil(fieldValue))
  				searchInput += fieldValue;
  		}

  		fieldMap['predicted_search_query'] = searchInput; // concatenation of the values from selected prediction fields
  	}
  	
  	// update the prediction results.
  	global.AutoResolutionPredictionHelper.updatePredictionResults(languageXResult.prediction_id, fieldMap, this.LOGGER);
  },
  
  
  /**
   * Executes post-processing extension point if found.
   * @param configGr
   * @param languageXResult : the result from LanguageX API
   * @return null if no pre-processing script is not found or error happened
   */
  _executePostProcessingExtension: function(languageXResult) {

  	var scriptSysId = this._getExtensionScriptSysId(AutoResolutionConstants.POST_PROCESSING_EXT_POINT_FIELD_NAME);

  	if (gs.nil(scriptSysId)) {
  		this.LOGGER.info("Auto-Resolution Configuration doesn't have post-processing extension script associated with it");
  		return null;
  	}

  	// get the param bag_getParamBagForPreProcessExtension
  	var paramBag = this._getParamBagForPostProcessExtension(languageXResult);

  	// execute the script with the param bag
  	return this.executeExtensionScript(scriptSysId, paramBag);
  },

  /**
   * Returns the pre/post extension script Sys Id
   * @return null if not found
   */
  _getExtensionScriptSysId: function(fieldName) {

  	// Get the ref to the extension instance record
  	var extInstanceRef = this.configGr[fieldName];

  	if (gs.nil(extInstanceRef)) {
  		this.LOGGER.info('{0} is not set for {1}. Stopped executing the extension', fieldName, this.configId);
  		return null;
  	}

  	var scriptRef = extInstanceRef['script_include'];

  	if (gs.nil(scriptRef)) {
  		this.LOGGER.info('Extension Point:{0} has no associated script for {1}. Stopped executing the extension', extInstanceRef.point, fieldName);
  		return null;
  	}

  	if (!extInstanceRef.active) {
  		this.LOGGER.info('Extension Point:{0} : {1} is currently inactive. Stopped executing the extension', extInstanceRef.point, scriptRef.name);
  		return null;
  	}

  	return scriptRef.sys_id;
  },

  /**
   * Executes script include that is associated with the extension point
   *
   * @param scriptSysId : the associated script sysId
   * @param paramBag : the parameter bag to be passed to the pre / post extension script
   * @return null if scirpt is not found.
   */
  executeExtensionScript: function(scriptSysId, paramBag) {

  	var scriptGr = new GlideRecord('sys_script_include');

  	if (!scriptGr.get(scriptSysId)) {
  		this.LOGGER.error("Could not find the associated script with sys_id:{0} for pre / post extension ", scriptSysId);
  		return null;
  	}

  	// execute the script.
  	try {
  		var gse = new GlideScopedEvaluator(scriptGr);
  		var obj = gse.evaluateScript(scriptGr);

  		return obj.process(this.LOGGER, paramBag);
  	} catch(e) {
  		this.LOGGER.error("An error occurred while executing the extension script: [{0}]. parameterBag:{1}, error:{2}",
  			scriptGr.getValue('name'), JSON.stringify(paramBag), e);
  		throw e;
  	}
  },

  /**
   * Returns the parameter bag for the pre-process extension
   * @param configGr : the config record
   * @param taskGr :the task record
   * @return a new parameter bag
   */
  _getParamBagForPreProcessExtension: function() {
  	var res = {};
  	res.ar_config_id = this.configId;
  	res.task_table_name = this.taskGr.getTableName();
  	res.task_sys_id = this.taskId;
  	return res;
  },

  /**
   * Returns the parameter bag for the post-processing
   * @param configGr: the config record
   * @param languageXResult: the languageX result in json format
   * @return the parameter bag
   */
  _getParamBagForPostProcessExtension: function(languageXResult) {
  	var res = {};
  	res.ar_config_id = this.configId;
  	res.language_x_result = languageXResult;
  	return res;
  },

  type: 'AutoResolutionLanguageXStage'
});

Sys ID

f1a6c8a253220110af71ddeeff7b1290

Offical Documentation

Official Docs: