Update Dynamic Entity Values
Dynamic entities are special entities that you can update by calling the dynamic entity APIs. Here's how to use JavaScript to add, modify, and delete a dynamic entity's values from information that's in a JSON file.
Disclaimer: This example is provided as-is, with no support provided by Oracle world-wide customer support.
Create the Folder Structure and Add Files
Follow these steps to create the folder structure and add the necessary files.
-
Create the top-level folder. For example:
dynamic-entities-import
. -
Add a
package.json
file with the following content:{ "name": "dynamic-entities-import", "version": "2.0.0", "description": "This is the code for the dynamic entities import use case in the REST Reference.", "main": "main.js", "scripts": { "start": "node src/main.js", "start:dev": "nodemon --inspect src/main.js", "lint": "eslint .", "lint:fix": "eslint . --fix" }, "keywords": [], "author": "", "dependencies": { "log4js": "^6.3.0", "oci-common": "^2.1.0" }, "devDependencies": { "eslint": "^7.32.0", "nodemon": "^2.0.12" } }
-
In the top-level folder, create
config
andsrc
subfolders. -
Add a file named
config.js
to theconfig
folder and add this content:/* * Logging level ALL|INFO|DEBUG|WARN|ERROR|FATAL|OFF|TRACE */ module.exports.LOGGING_LEVEL = 'INFO'; /* * OCI request signature configurations */ // OCI CLI config file module.exports.OCI_CONFIG_FILE_PATH = '~/.oci/config'; // OCI CLI profile name module.exports.OCI_CONFIG_PROFILE_NAME = 'DEFAULT'; /* * Oracle Digital Assistant configurations */ // Digital Assistant host name without "https://" module.exports.ODA_HOSTNAME= ''; // API base path - do not modify module.exports.DYNAMIC_ENTITIES_API_BASE_PATH = '/api/v1'; /* * Run configurations */ // Full path of the JSON file that contains the patch data module.exports.DYNAMIC_ENTITIES_PATCH_DATA_PATH = '~/patchData.json'; // Name of the skill to upload the dynamic entities to module.exports.ODA_SKILL_NAME = ''; // Version of the named skill module.exports.ODA_SKILL_VERSION = ''; // Name of the dynamic entity to modify module.exports.ODA_ENTITY_NAME = ''; // Set copy to true to retain the original value set module.exports.DYNAMIC_ENTITIES_COPY = true; /* * Constants */ // Maximum number of tries to check push request status module.exports.MAX_STATUS_RETRIES = 50; // Active export task statuses - do not modify module.exports.ACTIVE_STATUSES= ['INPROGRESS', 'TRAINING'];
-
In the
src
folder, create a file namedmain.js
, and then add this code:const CONFIG = require('../config/config'); const ODAManager = require('./lib/ODAManager'); const log4js = require('log4js'); const logger = log4js.getLogger('Dynamic Entities Importer'); logger.level = CONFIG.LOGGING_LEVEL; // Start insights export job ODAManager.startDynamicEntityImport() .then( () => {logger.info('Dynamic entity updated.');}) .catch(error => { logger.debug(error); logger.info('Could not update dynamic entity with patch data.'); });
-
Create a
lib
subfolder undersrc
. -
Complete the instructions in the Send Signed Requests use case to add
OCIManager.js
to thelib
directory, create the OCIconfig
file, and update the package'sconfig/config.js
file to provide the OCI request signature configurations. -
In the
lib
folder, createODAManager.js
, and then add this content.const CONFIG = require('../../config/config'); const OCIManager = require('./OCIManager'); const os = require('os'); const log4js = require('log4js'); const logger = log4js.getLogger('ODA Manager'); logger.level = CONFIG.LOGGING_LEVEL; class ODAManager { constructor() { } /** * Push patch data to dynamic entity */ async startDynamicEntityImport() { let dataPath = (CONFIG.DYNAMIC_ENTITIES_PATCH_DATA_PATH.indexOf('~/') === 0) ? CONFIG.DYNAMIC_ENTITIES_PATCH_DATA_PATH.replace('~', os.homedir()) : CONFIG.DYNAMIC_ENTITIES_PATCH_DATA_PATH; try { // Make sure it's a valid path require(dataPath); } catch (error) { const errorMessage = `Can't open the patch data file ${CONFIG.DYNAMIC_ENTITIES_PATCH_DATA_PATH} or it isn't valid JSON`; logger.error(errorMessage); throw (error); } try { // Get bot ID for identified skill const botId = await this._lookupBotId(CONFIG.ODA_SKILL_NAME, CONFIG.ODA_SKILL_VERSION); // Get ID for named entity const entityId = await this._lookupEntityId(botId, CONFIG.ODA_ENTITY_NAME); // Make sure there are no active push requests for this entity const pushRequestsList = await this._getPushRequests(botId, entityId); await this._checkForActiveRequests(pushRequestsList); // Create the push request const pushRequestData = await this._createPushRequest(botId, entityId, (CONFIG.DYNAMIC_ENTITIES_COPY === undefined) ? false : CONFIG.DYNAMIC_ENTITIES_COPY); // Push the data const pushDataResponse = await this._pushDataToRequest(botId, entityId, pushRequestData.result.id, dataPath); // Wait for export job to finish await this._finalizePushRequest(botId, entityId, pushRequestData.result.id); const result = await this._trackStatus(botId, entityId, pushRequestData.result.id); // Conclusion if (CONFIG.ACTIVE_STATUSES.includes(result)) { logger.info(`The request job still hasn't completed. Current status is ${result}. Check job status later.`); } else { // Report the results logger.info(`The push request job has finished. Status = ${result}.`); if (result === 'COMPLETED') { logger.info(`Total deleted: ${pushDataResponse.result.totalDeleted}`); logger.info(`Total added: ${pushDataResponse.result.totalAdded}`); logger.info(`Total modified: ${pushDataResponse.result.totalModified}`); } } } catch (error) { const errorMessage = `Error updating dynamic entity. Detailed error: ${error.message} Request ID: ${error.opcRequestId}`; logger.error(errorMessage); throw (error); } } /** * Stop if there's another push request in process. * * @param pushRequestsList - Results from * GET /api/v1/bots/{botId}/dynamicEntities/{entityId}/pushRequests * @returns {object} - pushRequestsList */ async _checkForActiveRequests(pushRequestsList) { return new Promise(function (resolve, reject) { const activeRequests = pushRequestsList.result.filter(val => CONFIG.ACTIVE_STATUSES.includes(val.status.toUpperCase())); if (activeRequests.length > 0) { reject(new Error('Can\'t create a new request because push request ' + activeRequests[0].id + ' hasn\'t completed yet. Try again later.')); } else { resolve(pushRequestsList); } reject(new Error('Push request filter error.')); }); } /** * Monitor the push request until it finishes. * * Checks the status until the status is no longer an active status (INPROGRESS or TRAINING) * or the number of attempts to check the status exceeds CONFIG.MAX_STATUS_RETRIES. * * @param {string} botid * @param {string} entityId * @param {string} pushRequestId * @returns {Promise<string>} Status */ async _trackStatus(botId, entityId, pushRequestId) { try { logger.info(`Bot ID: ${botId}`); logger.info(`Entity ID: ${entityId}`); logger.info(`Push Requst ID: ${pushRequestId}`); logger.info('The push request job ${pushRequestId} is in progress. Waiting for the job to finish.'); var pushRequestStatus = 'STATUS NOT YET RETRIEVED'; let attemptsCount = 0; const exponentialBackOffFactor = 3; while (true) { const pushRequestData = await this._getPushRequest(botId, entityId, pushRequestId); pushRequestStatus = pushRequestData.result.status.toUpperCase(); if (CONFIG.ACTIVE_STATUSES.includes(pushRequestStatus)) { // Not done yet // Check if maximum number of retries reached if (++attemptsCount > CONFIG.MAX_STATUS_RETRIES) { break; } // Wait before retrying by implementing exponential back-off strategy using a factor of 3 logger.warn('The push request job is still in progress.'); await this._sleep(attemptsCount * exponentialBackOffFactor * 1000); // ex. ( 1 (attemptCount) * 3 exponentialBackOffFactor ) seconds * 1000 milliseconds } else { break; } } return pushRequestStatus; } catch (error) { const errorMessage = `Error tracking push request status. Detailed error: ${error.message} Request ID: ${error.opcRequestId}`; logger.error(errorMessage); throw new Error(errorMessage); } } /** * Wait for a specified time. * * @param {number} duration - Wait duration in milliseconds * @returns */ sleep(duration) { return new Promise(resolve => setTimeout(resolve, duration)); } /** * Get list of skills * * @returns {object} Results for GET skills */ async _listSkills() { try { const URI = `https://${CONFIG.ODA_HOSTNAME}${CONFIG.DYNAMIC_ENTITIES_API_BASE_PATH}/skills`; return await OCIManager.send(URI, 'GET'); } catch (error) { const errorMessage = `Error getting list of skills. Detailed error: ${error.message} Request ID: ${error.opcRequestId}`; logger.error(errorMessage); throw new Error(errorMessage); } } /** * Given a skills list, find the matching skill name and version * and then return the bot ID. * * @param {object} skillsData - Response from GET /api/v1/skills * @param {string} skillName - User-supplied name of skill * @param {string} skillVersion - User-supplied version of the skill * @returns {Promise <string>} Bot ID */ async _getBotIdForSkillNameVersion(skillsData, skillName, skillVersion) { try { const matchingSkills = skillsData.result.filter(val => val.name === skillName && val.version === skillVersion); if (matchingSkills.length === 0) { throw new Error(`Cannot find the skill with the name ${skillName} and version ${skillVersion}.`); } else { return(matchingSkills[0].id); } } catch (error) { const errorMessage = `Error getting the bot ID for ${skillName} ${skillVersion}. Detailed error: ${error.message} Request ID: ${error.opcRequestId}`; logger.error(errorMessage); throw new Error(errorMessage); } } /** * Get the skills list and search it to get the bot ID for * the matching skill name and version. * * @param {string} skillName - User-supplied name of skill * @param {string} skillVersion - User-supplied version of the skill * @returns {Promise<string>} Bot ID */ async _lookupBotId(skillName, skillVersion) { try { return await this._listSkills().then(skillsData => this._getBotIdForSkillNameVersion(skillsData, skillName, skillVersion)); } catch (error) { const errorMessage = `Error getting the bot ID for ${skillName} ${skillVersion}. Detailed error: ${error.message} Request ID: ${error.opcRequestId}`; logger.error(errorMessage); throw new Error(errorMessage); } } /** * Get the list of the skill's entities. * * @param {string} botId * @returns {object} Results from GET bots/{botId}/dynamicEntities */ async _getEntitiesForSkill(botId) { try { const URI = `https://${CONFIG.ODA_HOSTNAME}${CONFIG.DYNAMIC_ENTITIES_API_BASE_PATH}/bots/${encodeURIComponent(botId)}/dynamicEntities`; return await OCIManager.send(URI, 'GET'); } catch (error) { const errorMessage = `Error getting the list of the skill's entities. Detailed error: ${error.message} Request ID: ${error.opcRequestId}`; logger.error(errorMessage); throw new Error(errorMessage); } } /** * Get the entity ID for the specified entity name from the entities data. * * @param {object} entitiesData - Response from GET /api/v1/bots/{botId}/dynamicEntities * @param {string} entityName - User-entered entity name * @returns {Promise<string>} Entity ID */ async _getEntityId(entitiesData, entityName) { return new Promise((resolve, reject) => { const matchingEntities = entitiesData.result.filter(val => val.name === entityName); if (matchingEntities.length === 0) { reject(new Error(`Cannot find an entity with the name ${entityName}.`)); } else { resolve(matchingEntities[0].id); } reject(new Error('Entity filter error.')); }); } /** * Get the entity ID for the specified entity name and bot ID. * * @param {string} botId * @param {string} entityName User-entered entity name * @returns {Promise<string>} Entity ID */ async _lookupEntityId(botId, entityName) { try { return await this._getEntitiesForSkill(botId).then(EntitiesData => this._getEntityId(EntitiesData, entityName)); } catch (error) { const errorMessage = `Error looking up the entity ID for ${entityName} Request ID: ${error.opcRequestId}`; logger.error(errorMessage); throw new Error(errorMessage); } } /** * Create push request. This creates a container for * add, delete, and modify instructions for the * dynamic entity (patch data). * * @param {string} botId * @param {string} entityId * @param {boolean} copyQueryParm - Set to true to retain the original set, false to replace it * @returns {object} Response from POST bots/{botId}/dynamicEntities/{entityId}/pushRequest?copy={copyQueryParm} */ async _createPushRequest(botId, entityId, copyQueryParm) { try { const body = '{}'; const URI = `https://${CONFIG.ODA_HOSTNAME}${CONFIG.DYNAMIC_ENTITIES_API_BASE_PATH}/bots/${encodeURIComponent(botId)}/dynamicEntities/${encodeURIComponent(entityId)}/pushRequests?copy=${copyQueryParm}`; // Send request to start the job to push patch data to the dynamic entity return await OCIManager.send(URI, 'POST', body); } catch (error) { const errorMessage = `Error starting the push request job. Detailed error: ${error.message} Request ID ${error.opcRequestId}`; logger.error(errorMessage); throw error; } } /** * Push data to the push request. Uploads add, delete, and modify * instructions to the push request's container. Gets the instructions * from the specified JSON file * * @param {string} botId * @param {string} entityId * @param {string} pushRequestId - The job ID that was returned when the patch request was created * @param {string} patchDataPath - The full path to the JSON file that contains the patch data * @returns {object} Response from PATCH bots/{botId}/dynamicEntities/{entityId}/pushRequests/{pushRequestId}/values * */ async _pushDataToRequest(botId, entityId, pushRequestId, patchDataPath) { try { const body = require(patchDataPath); const URI = `https://${CONFIG.ODA_HOSTNAME}${CONFIG.DYNAMIC_ENTITIES_API_BASE_PATH}/bots/${encodeURIComponent(botId)}/dynamicEntities/${encodeURIComponent(entityId)}/pushRequests/${encodeURIComponent(pushRequestId)}/values`; return await OCIManager.send(URI, 'PATCH', body); } catch (error) { const errorMessage = `Error sending patch data to push request job ${pushRequestId}. Detailed error: ${error.message} Request ID ${error.opcRequestId}`; logger.error(errorMessage); throw error; } } /** * Mark the push request as DONE so that it can start processing patch data, * train the entity, and then save the changes. There isn't any response. * You can optionally use the method to abort the push request. * * @param {string} botId * @param {string} entityId * @param {string} pushRequestId * @param {boolean} abort optional parameter that when set to true * aborts the push request instead of finalizing it. Default false. * @returns */ async _finalizePushRequest(botId, entityId, pushRequestId, abort) { try { let action = (abort) ? 'ABORT' : 'DONE'; // todo in case the above doesn't work //let action = 'DONE'; //if (abort) { // action = 'ABORT'; //} const body = '{}'; const URI = `https://${CONFIG.ODA_HOSTNAME}${CONFIG.DYNAMIC_ENTITIES_API_BASE_PATH}/bots/${encodeURIComponent(botId)}/dynamicEntities/${encodeURIComponent(entityId)}/pushRequests/${encodeURIComponent(pushRequestId)}/${encodeURIComponent(action)}`; return await OCIManager.send(URI, 'PUT', body); } catch (error) { const errorMessage = `Error finalizing push request. Detailed error: ${error.message} Request ID ${error.opcRequestId}`; logger.error(errorMessage); throw error; } } /** * Get push requests * * Returns response from PATCH /api/v1/bots/{botId}/dynamicEntities/{entityId}/pushRequests * * @param botId * @param entityId * @returns Response from GET /api/v1/bots/{botId}/dynamicEntities/{entityId}/pushRequests */ async _getPushRequests(botId, entityId) { try { const URI = `https://${CONFIG.ODA_HOSTNAME}${CONFIG.DYNAMIC_ENTITIES_API_BASE_PATH}/bots/${encodeURIComponent(botId)}/dynamicEntities/${encodeURIComponent(entityId)}/pushRequests`; return await OCIManager.send(URI, 'GET'); } catch (error) { const errorMessage = `Error getting push requests. Detailed error: ${error.message} Request ID ${error.opcRequestId}`; logger.error(errorMessage); throw error; } } /** * Get push request * * @param {string} botId * @param {string} entityId * @param {string} pushRequestId The job ID * @returns Response from GET /api/v1/bots/{botId}/dynamicEntities/{entityId}/pushRequests/{id} */ async _getPushRequest(botId, entityId, pushRequestId) { try { const URI = `https://${CONFIG.ODA_HOSTNAME}${CONFIG.DYNAMIC_ENTITIES_API_BASE_PATH}/bots/${encodeURIComponent(botId)}/dynamicEntities/${encodeURIComponent(entityId)}/pushRequests/${encodeURIComponent(pushRequestId)}`; return await OCIManager.send(URI, 'GET'); } catch (error) { const errorMessage = `Error getting push request. Detailed error: ${error.message} Request ID ${error.opcRequestId}`; logger.error(errorMessage); throw error; } } } module.exports = new ODAManager();
-
From a command terminal, change to the top level directory and run this command to install the libraries:
npm install
Prepare the Dynamic Entity Data
Create a JSON file that contains the request body (the values to add, modify, and delete). Here's the schema:
{
"type":"object",
"properties":{
"delete":{
"type":"array",
"description":"The entity values to delete. The service ignores this object if the copy query parameter for the push request was omitted or set to FALSE.",
"items":{
"type":"object",
"properties":{
"canonicalName":{
"type":"string",
"description":"The entity value."
},
"synonyms":{
"type":"array",
"description":"Synonyms for the entity value.",
"items":{
"type":"string",
"description":"Synonyms for the entity value."
}
},
"primaryLanguageCanonicalName":{
"type":"string",
"description":"The entity value (canonicalName) for the primary language. This property is required for secondary-language entity values for native multi-language skills. If you include it for a primary-language entity value, then it must be equal to the canonicalName value. The property must not be included for skills that aren't native multi-language."
},
"nativeLanguageTag":{
"type":"string",
"description":"The native language tag to use for the entity value. The tag must identify one of the languages that the skill supports. This property is required for native multi-language skills, even for entries for the primary language. This property is not valid for skills that aren't native multi-language."
}
}
}
},
"add":{
"type":"array",
"description":"The entity values to add.",
"items":{
see delete
}
},
"modify":{
"type":"array",
"description":"The entity values to modify. The service ignores this object if the <code>copy</code> query parameter for the push request was omitted or set to <code>FALSE</code>.",
"items":{
see delete
}
}
},
"description":"The data to add, delete, and modify."
}
Here's an example for a native multi-language skill. Note that the native language tag is the first 2 letters of the natively-supported language's ISO 639-1 code.
{
"add": [{
"canonicalName": "department",
"synonyms": ["division"],
"nativeLanguageTag": "en"
}, {
"canonicalName": "abteilung",
"synonyms": ["bereich"],
"primaryLanguageCanonicalName": "department",
"nativeLanguageTag": "de"
}, {
"canonicalName": "departement",
"primaryLanguageCanonicalName": "department",
"nativeLanguageTag": "fr"
}
]
}
Here's an example for a skill that isn't native multi-language:
{
"add": [{
"canonicalName": "ABZ",
"synonyms": ["Aberdeen", "Aberdeen Dyce"]
}, {
"canonicalName": "AAL",
"synonyms": ["Aalborg"]
}, {
"canonicalName": "JAN",
"synonyms": ["Jackson-Medgar Wiley Evers International", "Jackson International"]
}
]
}
Run the Script
-
Open the
config/config.js
file and set the desired run configurations including the path to the JSON file that contains the request body, as described in the previous section./* * Run configurations */ // Full path of the JSON file that contains the patch data module.exports.DYNAMIC_ENTITIES_PATCH_DATA_PATH = '~/patchData.json'; // Name of the skill to upload the dynamic entities to module.exports.ODA_SKILL_NAME = ''; // Version of the named skill module.exports.ODA_SKILL_VERSION = ''; // Name of the dynamic entity to modify module.exports.ODA_ENTITY_NAME = ''; // Set copy to true to retain the original value set module.exports.DYNAMIC_ENTITIES_COPY = true;
-
From a terminal, change to the top-level directory, and then enter this command to run the script:
npm start
You should see output similar to this:
[2021-09-14T12:14:33.489] [INFO] ODA Manager - Entity ID: 25CB0ED2-2D9B-44B5-911F-7A95AD31EFD1 [2021-09-14T12:14:33.490] [INFO] ODA Manager - Push Requst ID: DADECC1A-8744-4F49-9FD6-DF0F82F6AD83 [2021-09-14T12:14:33.490] [INFO] ODA Manager - The push request job DADECC1A-8744-4F49-9FD6-DF0F82F6AD83 is in progress. Waiting for the job to finish. [2021-09-14T12:14:34.075] [INFO] ODA Manager - The push request job has finished. Status = COMPLETED. [2021-09-14T12:14:34.076] [INFO] ODA Manager - Total deleted: 0 [2021-09-14T12:14:34.076] [INFO] ODA Manager - Total added: 3 [2021-09-14T12:14:34.077] [INFO] ODA Manager - Total modified: 0 [2021-09-14T12:14:34.077] [INFO] Dynamic Entities Importer - Dynamic entity updated.