Name

global.NLUParloIntegrator

Description

An interface between Platform ML Service.

Script

var NLUParloIntegrator = Class.create();

(function() {
  var nluService = sn_ml.MLServiceUtil;
  var constants = NLUConstants.constants;
  var workflowConstants = WorkflowSolutionUtils.constants;
  var tables = NLUConstants.tables;
  var stateTypes = NLUConstants.MODEL_STATE_TYPES;
  var sysProps = NLUConstants.SYS_PROPS;

  function getSupportedFeaturesByLanguageAndVersion(language, version, queryFilter) {
      var queryCondition = 'supportedFeaturesByLanguageAndVersion(language: ' + language + ',version:\"' + version + '\")';
      var res = NLUParloIntegrator.getCapability('query { NLU { ' + queryCondition + ' { ' + queryFilter + ' } } }');
      if (res.state == 'success' && res.data) {
          return res.data.NLU.supportedFeaturesByLanguageAndVersion;
      }
  }

  NLUParloIntegrator.getModelSolutionName = function(modelLabel) {
      //Create a solution with dummy authoring model and add it to the solution store to get the solution name
      var authoringModel = {
          'dummyKey': 'dummyValue'
      };
      var solution = new sn_ml.NLUSolution({
          'label': modelLabel,
          "schedulingInfo": {
              "useCase": "Train NLU Solution"
          }
      }, authoringModel);
      var solutionName = sn_ml.NLUSolutionStore.add(solution);
      return solutionName;
  };
  NLUParloIntegrator.getSolution = function(modelName) {
      try {
          var labelOptions = modelName.match(/^ml_.*/) === null ? {
              useLabel: true
          } : {};
          return sn_ml.NLUSolutionStore.get(modelName, labelOptions);
      } catch (ex) {
          gs.error('Failed to get info for ' + modelName);
      }
      return null;
  };
  NLUParloIntegrator.getSolutionVersions = function(solution) {
      //Building solution version info
      var solutionVersionInfoArray = [];
      try {
          var solutionVersionDetails = JSON.parse(solution.getSolutionVersionDetails());
          if (solutionVersionDetails && solutionVersionDetails.status === 'success')
              solutionVersionInfoArray = solutionVersionDetails.response.versions;
      } catch (e) {
          gs.debug('NLU model getSolutionVersions error : ' + e.message);
      }
      gs.debug('NLU Model Versions information : ' + JSON.stringify(solutionVersionInfoArray));
      return solutionVersionInfoArray;
  };
  NLUParloIntegrator.getLastTrainedVersion = function(solution) {
      var solDefGr = NLUParloIntegrator.getSolutionDefinition(solution);
      try {
          if (solDefGr) {
              var solutionVersionNum = parseInt(solDefGr.getValue('current_solution_version') || 0);
              return solutionVersionNum > 0 && solution.getVersion(solutionVersionNum);
          }
      } catch (ex) {
          gs.debug("Exception fetching solution version: ", ex);
      }
      return null;
  };
  NLUParloIntegrator.getSolutionDefinition = function(solution) {
      var solutionName = solution.getName();
      var gr = new GlideRecord(tables.ML_CAPABILITY_DEFINITION_BASE);
      gr.addQuery('solution_name', solutionName);
      gr.setLimit(1);
      gr.query();
      return gr.next() && gr;
  };
  NLUParloIntegrator.getDefinitionUpdatedOn = function(solution) {
      var solDefGr = NLUParloIntegrator.getSolutionDefinition(solution);
      return solDefGr && solDefGr.getValue('sys_updated_on');
  };
  NLUParloIntegrator.getStatusFromSolution = function(solution) {
      var result = {
          state: 'draft'
      };
      var pCreatedOn = 0,
          tCreatedOn = 0,
          iCreatedOn = 0,
          createdOn, details;
      var mlSolutionUpdatedOn = null;
      var mlSolutionCreatedOn = null;
      var tVersion = 0,
          pVersion = 0,
          iVersion = 0;
      try {
          var lastVersionObj = NLUParloIntegrator.getLastTrainedVersion(solution);
          var lastVersion = lastVersionObj ? parseInt(lastVersionObj.getVersionNumber()) : 0;
          var modelVersions = NLUParloIntegrator.getSolutionVersions(solution);
          var len = modelVersions.length;
          for (var i = len - 1; i >= 0; i--) {
              details = modelVersions[i];
              var version = parseInt(details.version);
              // Ignore all the versions after current_version of capability definition:
              if (version > lastVersion) continue;
              createdOn = new GlideDateTime(details.sys_created_on).getNumericValue();
              if (details.active == 'true' && createdOn > pCreatedOn) {
                  pCreatedOn = createdOn;
                  pVersion = version;
                  result.lastPublishedBy = NLUSystemUtil.getUserName(details.sys_updated_by);
                  result.lastPublishedOn = details.sys_updated_on;
              }
              if (createdOn > tCreatedOn) {
                  if (details.state === constants.SOLUTION_COMPLETE) {
                      tCreatedOn = createdOn;
                      tVersion = version;
                      mlSolutionUpdatedOn = details.sys_updated_on;
                      mlSolutionCreatedOn = details.sys_created_on;
                  } else if (details.hasJobEnded === 'false') {
                      iCreatedOn = createdOn;
                      iVersion = version;
                  }
              }
          }
          result.trainedVersion = tVersion;
          result.publishedVersion = pVersion;
          result.trainingVersion = iVersion;
          if (tCreatedOn)
              result.lastTrainedOn = mlSolutionUpdatedOn;
          if (iCreatedOn > tCreatedOn) {
              result.state = stateTypes.training;
          } else if (tCreatedOn > pCreatedOn) {
              result.state = stateTypes.trained;
          } else if (tCreatedOn && pCreatedOn && tCreatedOn === pCreatedOn) {
              result.state = stateTypes.published;
              // When last trained version is published, we can't use ml_solutions.updated_on for trainedOn value.
              // - If latest ml_solution is successful, then use updated_on of corresponding ml_capability_definition record.
              // - Else use the ml_solution.created_on
              if (result.trainedVersion === lastVersion) {
                  result.lastTrainedOn = NLUParloIntegrator.getDefinitionUpdatedOn(solution);
              } else {
                  result.lastTrainedOn = mlSolutionCreatedOn;
              }
          }
          result.status = 'success';
          gs.debug('NLU Model Status : ' + JSON.stringify(result));
      } catch (e) {
          result.status = 'failure';
          gs.debug('NLU Model getModelStatus error : ' + e.message);
      }
      return result;
  };
  NLUParloIntegrator.prototype = {
      type: 'NLUParloIntegrator',
      initialize: function(modelGr) {
          this.setModelGr(modelGr);
      },
      setModelGr: function(modelGr) {
          this.modelGr = modelGr;
          // If the model name does not start with 'ml_', it is a Pre-Quebec model
          this.labelOptions = modelGr.getValue('name').match(/^ml_.*/) === null ? labelOptions = {
              useLabel: true
          } : {};
      },
      _getPayload: function(options) {
          var payload = [{
              solutionName: this.modelGr.getValue('name'),
              solutionDomain: this.modelGr.getValue('sys_domain'),
              solutionScope: this.modelGr.getValue('sys_scope')
          }];
          if (options) {
              var domain = NLUSystemUtil.getCurrentDomain();
              if (domain) options['solutionDomain'] = domain;
              NLUHelper.extend(payload[0], options);
          }
          return JSON.stringify(payload);
      },
      getSolution: function() {
          var modelName = this.modelGr.getValue('name');
          return sn_ml.NLUSolutionStore.get(modelName, this.labelOptions);
      },
      getLatestVersion: function() {
          var solution = this.getSolution();
          var latestVersion = solution.getLatestVersion();
          if (latestVersion) return parseInt(latestVersion.getVersionNumber());
          return 0;
      },
      getModelVersion: function(solution) {
          if (!solution) solution = this.getSolution();
          var output = JSON.parse(solution.getModelVersion(NLUModel.getLanguage(this.modelGr), {
              "request_purpose": this.modelGr.getValue('category')
          }));
          return output.response.version;
      },
      getModelStatus: function() {
          try {
              var solution = this.getSolution();
              return NLUParloIntegrator.getStatusFromSolution(solution);
          } catch (e) {
              return {
                  status: 'failure',
                  message: e.message
              };
          }
      },
      train: function(trainData, options) {
          var trainDataStr = JSON.stringify(trainData);
          gs.debug('NLU Model JSON : ' + trainDataStr);
          var output = {};
          try {
              var modelName = this.modelGr.getValue('name');
              var isL3Model = this.isAsync();
              var intentCount = trainData.intents && trainData.intents.length || 0;
              var maxIntentsCount = parseInt(gs.getProperty(sysProps.MAX_INTENTS_FOR_SYNC_TRAIN, 0));
              var utteranceCount = intentCount > 0 && trainData.intents.reduce(function(result, intentData) {
                  return result + (intentData && intentData.samples && intentData.samples.length || 0);
              }, 0) || 0;
              var maxUtteranceCount = parseInt(gs.getProperty(sysProps.MAX_UTTERANCES_FOR_SYNC_TRAIN, 0));
              var isAsync = utteranceCount > maxUtteranceCount || intentCount > maxIntentsCount || isL3Model;
              var workflowAsyncCapability = isAsync && this.isWorkflowAsyncSupportedForVersion(trainData.version);
              var trainingOptions = {
                  "trainingMode": isAsync ? "async" : "sync"
              };
              var solutionInfo = {
                  "label": modelName,
                  "trainingMode": isAsync ? "async" : "sync",
                  "description": workflowConstants.WORKFLOW_NLU_USECASE_NAME,
                  "workflowAsyncCapability": workflowAsyncCapability,
                  "schedulingInfo": {
                      "useCase": workflowConstants.WORKFLOW_NLU_USECASE_NAME
                  },
                  "tags": [
                      "Train NLU Solution"
                  ],
                  "workflowConfiguration": {
                      "trainingFrequency": "run_once"
                  },
                  "trainingPipeline": {
                      "pipelineInput": {
                          "datasets": {
                              // DON'T REMOVE : Required by WorkflowSolution Configuration Validation
                          },
                          "config": {
                              "authoring_model": trainData
                          },
                          "solutions": {
                              // DON'T REMOVE : filed dynamically by NLUSolution
                          }
                      },
                      "pipelineName": "nlu_training_workflow_v1"
                  }
              };
              solutionInfo.nluTrainingMode = "";
              if (!isAsync || isL3Model)
                  solutionInfo.nluTrainingMode = "IntentClassifier";
              var ultDataSet = this.getUtteranceLabelDataSet(modelName);
              if (ultDataSet) {
                  solutionInfo.trainingPipeline.pipelineInput.datasets["utterance_label_table"] = ultDataSet;
                  solutionInfo.trainingPipeline.pipelineInput.config["use_utterance_label_table"] = gs.getProperty(sysProps.USE_ML_LABELED_TABLE) || "false";
              }
              var solutionOpts = this.labelOptions;
              if (options && typeof options === 'object') {
                  solutionOpts = NLUHelper.extend(solutionOpts, options);
              }
              var updatedSolution = new sn_ml.NLUSolution(solutionInfo, JSON.parse(trainDataStr));
              sn_ml.NLUSolutionStore.update(modelName, updatedSolution, solutionOpts);
              if (options && typeof options === 'object') {
                  trainingOptions = NLUHelper.extend(trainingOptions, options);
              }
              var solutionVersion = updatedSolution.submitTrainingJob(trainingOptions);
              var solutionStatus = JSON.parse(solutionVersion.getStatus());
              if (solutionStatus.hasJobEnded === 'false')
                  output.status = 'training';
              else {
                  output.status = solutionStatus.state === constants.SOLUTION_COMPLETE ? 'success' : 'failure';
              }
              output.response = {
                  solutionVersion: solutionVersion.getVersionNumber()
              };
          } catch (e) {
              var errorMessage = e.message;
              if (errorMessage) {
                  var position = errorMessage.indexOf(constants.SYNCHRONOUS_TRAINING_FAILURE);
                  if (position === -1) {
                      position = errorMessage.indexOf(constants.VALIDATION_FAILURE);
                      if (position !== -1)
                          position = position + constants.VALIDATION_FAILURE.length;
                  } else {
                      position = position + constants.SYNCHRONOUS_TRAINING_FAILURE.length;
                  }
                  // Extract the error message for unknown words
                  if (position !== -1)
                      output = JSON.parse(errorMessage.substring(position));
                  else
                      throw e;
              } else {
                  output = gs.getMessage('Unable to train model at this point of time. Please try again later');
              }
          }
          gs.debug('NLU Model Train response : ' + JSON.stringify(output));
          return output;
      },
      cancelTraining: function() {
          var result = {};
          try {
              var nluSolution = this.getSolution();
              nluSolution.cancelTrainingJob();
              result.status = 'success';
          } catch (e) {
              gs.debug('NLU Model cancelTraining error' + e.message);
              result.status = 'failure';
              result.message = e.message;
          }
          return result;
      },
      validate: function(modelInfo) {
          var modelInfoStr = JSON.stringify(modelInfo);
          gs.debug('NLU Model Info : ' + modelInfoStr);
          var authoringModel = {};
          var solutionInfo = {
              "label": this.modelGr.getValue('name'),
              "schedulingInfo": {
                  "useCase": "Train NLU Solution"
              }
          };
          var solution = new sn_ml.NLUSolution(solutionInfo, authoringModel);
          var output = solution.validate(JSON.parse(modelInfoStr));
          gs.debug('NLU Model Validate response : ' + output);
          return JSON.parse(output);
      },
      predict: function(utterance, ctOverride, solutionVersion) {
          var inputJson = {
              utterance: utterance
          };
          var currDate = (new Date()).toJSON();
          var options = {
              confidenceThresholdOverride: ctOverride,
              clientRequestTime: currDate.split('T')[0]
          };
          var solution = this.getSolution();
          var nluSolutionVersion = solution.getVersion(solutionVersion);
          var results = nluSolutionVersion.predict(JSON.parse(JSON.stringify(inputJson)), options);
          gs.debug('NLU Model predict response : ' + results);
          return results;
      },
      getConfidenceThreshold: function(solutionVersion) {
          try {
              var solution = this.getSolution();
              var nluSolutionVersion = solution.getVersion(solutionVersion);
              var solutionVersionProperties = JSON.parse(nluSolutionVersion.getProperties());
              return solutionVersionProperties.authoringModel.confidenceThreshold;
          } catch (e) {
              gs.debug('NLU Model getConfidenceThreshold error : ' + e.message);
          }
          return 0;
      },
      publish: function(trainedVersion) {
          //Activating the specified version
          var solution = this.getSolution();
          solution.setActiveVersion(trainedVersion);
          var solutionVersion = solution.getVersion(trainedVersion);
          var solutionProperties = JSON.parse(solutionVersion.getProperties());
          var output = {
              'status': solutionProperties.isActive === 'true' ? 'success' : 'failure',
              'response': {
                  'solutionVersion': trainedVersion
              }
          };
          gs.debug('NLU Model Publish response : ' + JSON.stringify(output));
          return output;
      },
      getActiveSolutionVersion: function() {
          var solutionVersion;
          try {
              var solution = this.getSolution();
              var nluSolutionVersion = solution.getActiveVersion();
              solutionVersion = nluSolutionVersion.getVersionNumber();
          } catch (e) {
              gs.debug('NLU model getActiveSolutionVersion error: ' + e.message);
              solutionVersion = null;
          }
          return solutionVersion;
      },
      // Capability Cache:
      populateCapabilityCache: function(queryFilter) {
          var language = NLUModel.getLanguage(this.modelGr);
          var version = this.getModelVersion(this.getSolution());
          var data = getSupportedFeaturesByLanguageAndVersion(language, version, queryFilter);
          if (data) {
              this.capabilityCache = NLUHelper.extend({}, data);
          }
      },
      isAsync: function() {
          if (!this.capabilityCache || !this.capabilityCache.trainModes)
              this.populateCapabilityCache('trainModes');
          var trainModes = this.capabilityCache.trainModes;
          return trainModes &&
              Array.isArray(trainModes) &&
              trainModes.indexOf('async') > -1 &&
              trainModes.indexOf('sync') === -1;
      },
      isWorkflowAsyncSupportedForVersion: function(version) {
          try {
              if (!this.capabilityCache || !this.capabilityCache.releaseFeatures)
                  this.populateCapabilityCache('releaseFeatures { name, isSupported, nonSupportedVersions }');
              for (var i = 0; i < this.capabilityCache.releaseFeatures.length; i++) {
                  var feat = this.capabilityCache.releaseFeatures[i];
                  if ((feat.name || '').toLowerCase() === 'workflow_async' && feat.isSupported === true) {
                      return feat.nonSupportedVersions.indexOf(version) === -1;
                  }
              }
          } catch (e) {
              gs.error('Error while fetching async capabilities');
          }
          return false;
      },
      getSupportedSystemEntities: function() {
          var supportedNERs = [];
          if (!this.capabilityCache || !this.capabilityCache.allNers)
              this.populateCapabilityCache('allNers {name, isEnabled}');
          this.capabilityCache.allNers.forEach(function(ner) {
              if (ner.isEnabled) {
                  supportedNERs.push(ner.name);
              }
          });
          return supportedNERs;
      },
      getTrainedIntentNames: function(transformer) {
          var intentNames = [];
          try {
              var solVersion = NLUParloIntegrator.getLastTrainedVersion(this.getSolution());
              var propsJson = JSON.parse(solVersion.getProperties());
              propsJson.authoringModel.intents.forEach(function(intentData) {
                  intentNames.push(
                      transformer ? transformer(intentData.name) : intentData.name
                  );
              });
          } catch (ex) {
              gs.error('Failed to get trained intents for model: ' + this.modelGr.getUniqueValue());
          }
          return intentNames;
      },
      getUtteranceLabelDataSet: function(modelName) {
          var utteranceLabelData = null;
          var intentIds = this.getIntentIdsFromModelName(modelName);
          var encodedQuery = 'usage=nlu_model_train^product=nlu^label_typeINpositive,irrelevant,irrelevant_to_this_model^label_referenceIN' + intentIds.join(',');
          var ultGr = new GlideRecord(tables.ML_LABELED_DATA);
          ultGr.addEncodedQuery(encodedQuery);
          ultGr.query();
          if (ultGr.getRowCount() > 0) {
              utteranceLabelData = new sn_ml.DatasetDefinition({
                  'tableName': tables.ML_LABELED_DATA,
                  'fieldNames': ['text', 'label', 'label_type', 'correct_label', 'recommendation', 'source', 'product', 'usage', 'frequency', 'sys_domain'],
                  'encodedQuery': encodedQuery
              });
          }
          return utteranceLabelData;
      },
      getIntentIdsFromModelName: function(modelName) {
          var intentIds = [];
          var intentGr = new GlideRecord(tables.SYS_NLU_INTENT);
          intentGr.addEncodedQuery('enableISEMPTY^ORenable=true');
          intentGr.addQuery('model.name', modelName);
          intentGr.query();
          while (intentGr.next()) {
              intentIds.push(intentGr.getUniqueValue());
          }
          return intentIds;
      },
  };
  NLUParloIntegrator.getCapability = function(query) {
      var result = {};
      try {
          result = nluService.fetchGraphQL(query);
          if (result) {
              result = JSON.parse(result);
              result.state = 'success';
          } else {
              result.state = 'failure';
          }
      } catch (e) {
          result.state = 'failure';
          gs.debug('Error fetching capability: ' + e.message);
      }
      return result;
  };
  NLUParloIntegrator.getSupportedLanguages = function() {
      var res = this.getCapability("query { NLU { supportedLanguages { name, language, releaseFeatures { name, isSupported, nonSupportedVersions }, hasSupportForSearch, tokenType, intents { entities { isEnabled, supportsVocabSources } }, allNers {name, isEnabled}, trainModes } } }");
      var supportedLanguages = [];
      if (res.state == 'success' && res.data) {
          res.data.NLU.supportedLanguages.forEach(function(language) {
              var systemEntities = [];
              language.allNers.forEach(function(ner) {
                  if (ner.isEnabled) {
                      var translatedLabel = gs.getMessageLang('nlu_entity_' + ner.name, language.language);
                      systemEntities.push({
                          name: ner.name,
                          label: translatedLabel === 'nlu_entity_' + ner.name ? ner.name : translatedLabel
                      });
                  }
              });
              var ignorePunctuation = undefined;
              (language.releaseFeatures || []).forEach(function(feat) {
                  if (feat.name === "neural_model_punctuation_cleaning") {
                      ignorePunctuation = feat.isSupported;
                  }
              });

              var languageObject = {
                  label: gs.getMessage(language.name),
                  value: language.language,
                  intentOnly: !language.intents.entities.isEnabled,
                  supportsVocabSources: language.intents.entities.supportsVocabSources,
                  hasSupportForSearch: language.hasSupportForSearch,
                  tokenType: language.tokenType,
                  trainModes: language.trainModes,
                  systemEntities: systemEntities,
                  ignorePunctuation: ignorePunctuation
              };

              (languageObject.value.equals("en")) ? supportedLanguages.unshift(languageObject): supportedLanguages.push(languageObject);
          });
      }
      return supportedLanguages;
  };
  NLUParloIntegrator.getSupportedLanguageCodesByCategory = function(category) {
      if (category !== constants.MODEL_CAT_SEARCH) return NLUParloIntegrator.getSupportedLanguageCodes();
      var supportedLanguages = [];
      var result = NLUParloIntegrator.getSupportedLanguages();
      result.forEach(function(language) {
          if (language.hasSupportForSearch) supportedLanguages.push(language.value);
      });
      return supportedLanguages;
  };
  NLUParloIntegrator.getSupportedLanguageCodes = function() {
      var res = this.getCapability("query { NLU { supportedLanguages { language } } }");
      var supportedLanguages = [];
      if (res.state == 'success' && res.data) {
          res.data.NLU.supportedLanguages.forEach(function(language) {
              supportedLanguages.push(language.language);
          });
      }
      return supportedLanguages;
  };

  NLUParloIntegrator.isEntitiesEnabled = function(language, version) {
      var queryFilter = 'intents { entities { isEnabled } }';
      var data = getSupportedFeaturesByLanguageAndVersion(language, version, queryFilter);
      if (data) {
          return data.intents.entities.isEnabled;
      }
      return false
  };

  NLUParloIntegrator.getSupportedSystemEntities = function(language, version) {
      var supportedNERs = [];
      var queryFilter = 'allNers {name, isEnabled}';
      var data = getSupportedFeaturesByLanguageAndVersion(language, version, queryFilter);
      if (data) {
          data.allNers.forEach(function(ner) {
              if (ner.isEnabled) {
                  supportedNERs.push(ner.name);
              }
          });
      }
      return supportedNERs;
  };

  NLUParloIntegrator.getPunctuationAndThresholdCapabilities = function(language, version) {
      var queryFilter = 'name, modelThreshold, releaseFeatures { name, isSupported, nonSupportedVersions }';
      var data = getSupportedFeaturesByLanguageAndVersion(language, version, queryFilter);
      var capabilities = {};
      if (data) {
          capabilities["modelThreshold"] = data.modelThreshold;
          var releaseFeatures = data.releaseFeatures;
          (releaseFeatures || []).forEach(function(feat) {
              if (feat.name === 'neural_model_punctuation_cleaning') {
                  capabilities["isIgnoreEnabled"] = feat.isSupported;
              }
          });
      }
      return capabilities;
  };
})();

Sys ID

e3441d0007d51010220b0a701ad30033

Offical Documentation

Official Docs: