Provide Dynamic Tips for Improving User Interaction in Oracle Digital Assistant with Entity Event Handler

September 28, 2022 | 8 minute read
Text Size 100%:

Introduction

Oracle Digital Assistant provides an entity and composite bag feature. This feature allows developers to encapsulate business objects and automate entity resolutions from user's natural language input. However, the composite bag and entity alone only allow static configuration and resolution. With a recently released new feature called Entity Event Handler in ODA, developers can inject custom logic into the entity resolution process. This blog shows one way the entity event handler can be used to enhance user experience by providing users with a just-in-time helping tip.

Tutorials on ODA entity, composite bag and entity event handler can be found at ODA Tutorials.

Entity Event Handler

A composite bag entity can be associated with an event handler. ODA provides a skeleton Node.js based implementation for the event handler. The handler provides functions for developers to hook into the life cycle of an entity resolution. For example,

each entity in a composite bag has life cycle functions such as:

  • shouldPrompt : invoked when the associated entity is not resolved from user input
  • validate : invoked after a value is assigned to the entity

Each entity bag has the following life cycle hooks:

  • init : invoked before the entity resolution process starts
  • resolved: invoked after the entire entities in the composite bag are resolved

In this blog, we will injet into these life cycle events with custom logics to determine if the composite bag is completely resolved from a user input. If it is resolved, ODA will proceed to call the backend to retrieve information requested by the user. If the resolution is not complete, the custom logic in the event handler will direct ODA to prompt user for the missing information. After the resolution is complete, the event handler direct ODA to display a tip to the user so that they can be more efficient next time when they interact with the bot.

Demo Scenario and Requirement

In this demo use case, a manufacturing customer is using Oracle IoT (Internet of Things) cloud to monitor their factory operation status. This customer has two organizations namely Default and ATeam. The ATeam organization has two factories localted in east and west of US, which are named FactoryEast and FactoryWest. This customer is building a Chatbot application to allow users to quickly retrieve real time factory status information using natural language.

Implementation

To retrieve a factory status, a user needs to provide an organization name and a factory name. In the implementation below, we created a composite bag entity that includes two entities IoTOrg and IoTFactory. If a user enters "get ateam east status", The composite bag will resolve the IoTOrg to ateam and the IoTFactory to FactoryEast. It is a complete resolution and therefore, ODA will proceed to call IoT cloud for results. But if a new user only enters "get status", ODA will prompt the user to enter the organation and the factory. Then the ODA displays a tip to user so that they can bypass the prompt. 

Entity and Composite Bag Definitions

The following images show how the organization, factory and composite bag are defined.

Organization entity

 

Factory entity

 

Composite bag

 

Event Handler Implementation


'use strict';

const fetch = require("node-fetch");
const customPropertyName_showTip = 'showTip'; // variable, the tip is displayed only this variable is set to true

module.exports = {
  metadata: {
    name: 'iotStatusInputHandler',
    eventHandlerType: 'ResolveEntities',
    supportedActions: []
  },
  handlers: {
    items: {
      factory: {
        shouldPrompt: async (event, context) => {
          
          // If this function is called, it means the factory entity is not resolved.
          // So we will show the tip to the user.
          
          context.setCustomProperty(customPropertyName_showTip, true);
          return true;

        },

        validate: async (event, context) => {
          return true;
        },
      },

      org: {
        shouldPrompt: async (event, context) => {
          
          // If this function is called, it means the organization entity is not resolved.
          // So we will show the tip to the user.
          
          context.setCustomProperty(customPropertyName_showTip, true);
          return true;

        },

        validate: async (event, context) => {
          return true;
        },
      }
    },

    entity: {
      publishMessage: async (event, context) => {
        updatedItemsMessage(context);
        outOfOrderItemsMessage(context);
        context.addCandidateMessages();
      },

      init: async (event, context) => {
          
        // Initialize the showTip variable to false
          
        context.setCustomProperty(customPropertyName_showTip, false);

      },

      resolved: async (event, context) => {
          
        // If the factory entity is set to "Factory", remove the factory entity.
        // This is because "Factory" is not a defined factory name (FactoryEast and FactoryWest instead).
        // When querying IoT for status without a factory specified, the bot return the status from all factories under the org.
          
        if (context.getEntity().factory == 'Factory') {
          delete context.getEntity().factory;
        }
          
        // Construct the tip message only if showTip is enabled.
        // Otherwise, it means bot org and factory have been resolved so no need to display the tip.
          
        if (context.getCustomProperty(customPropertyName_showTip)) {
          var entity = context.getEntity();
          var factory = '';
          if (entity.factory && entity.factory == 'FactoryEast') {
            factory = 'east';
          }
          else if (entity.factory && entity.factory == 'FactoryWest') {
            factory = 'west';
          }
              
          context.addMessage('You can say "get ' + entity.org.toLowerCase() + ' ' + factory + ' status" to bypass prompts', true);
        }
          
      }
    }
  }
};

/**
 * Helper function to show acknowledgement message when a bag item value is updated.
 */
function updatedItemsMessage(context) {
  if (context.getItemsUpdated().length>0) {
    let message = "I have updated"+context.getItemsUpdated().map((item, i) => (i!==0 ? " and the " : " the ")+item.toLowerCase()+" to "+context.getDisplayValue(item));
    context.addMessage(message);
  }
}

/**
 * Helper function to show acknowledgement message when a bag item value is provided when user was prompted for anther bag item.
 */
function outOfOrderItemsMessage(context) {
  if (context.getItemsMatchedOutOfOrder().length>0) {
    let message = "I got"+context.getItemsMatchedOutOfOrder().map((item, i) => (i!==0 ? " and the " : " the ")+item.toLowerCase()+" "+context.getDisplayValue(item));
    context.addMessage(message);
  }
}

User Experience

The following images show how user experience looks like with the dynamic tip.

UserExperience1

UserExperience2

  1. A user enters a generic status query without an organization and a factory
  2. The bot prompts the user to enter an organization
  3. The bot prompts the user to enter a factory
  4. The bot displays a tip to user for bypassing prompts next time
  5. The bot shows the status result as requested by the user
  6. The user tries the tip with the bot
  7. The bot returns the result immediately without showing the prmpts and the tip again

I hope this blog has demonstrated one of many ways the entity event handler can be leveraged to make the user-bot interaction more friendly and easy-to-user.

 

Siming Mu


Previous Post

URL Filtering using OCI Network Firewall

Aditya Kulkarni | 7 min read

Next Post


Rootless podman on Oracle Enterprise Linux with local and kubernetes cluster DNS

Michael Shanley | 18 min read