article by Frank Nimphius, December 2022

In human conversations, information is often provided out of order, meaning some information is being passed while a user is prompted for something else.

In Oracle Digital Assistant, composite bag entities are a good way to process this information. However, there is a special use case, namely when the information is multi-valued. This is because users may want to do more than just replace existing information, they may want to add to it or remove from it.

This article explains how to use entity event handlers to implement the possible use cases of the tree: add, remove, replace

Introduction

The image below shows a composite bag entity implementation that uses a multi-value bag item "Color" for users to select one or many color names. When prompted the All Good? question, users can say yes, after which the selected colors get printed, or change the selected colors by typing a new set of color names. The multi-value bag item is handled through out-of-order extraction, which is a configurable feature of the composite bag entity. 

An out-of-order extraction occurs when users change the value of a bag item by typing in an appropriate value while being prompted for something else. So, instead of typing "yes" to acknowledge that all is good, you would type a new color name.

list of values shown by a multi-value bag item

In the image below, the colors should be changed to "blue and yellow" using out-of-order extraction. However, instead of changing the colors accordingly, a disambiguation dialog is shown and the conversation appears to be blocked.  The disambiguation dialog is shown because the underlying bag item does not know what you – the developer – want it to do with the new values.

Possible options are to

  • remove colors from an existing selection of colors
  • add colors to an existing selection of colors
  • replace an existing selection of colors

Since the disambiguation dialog appears, you can control what should happen according to your use case by creating an entity event handlers. In other words, an entity event handler is needed to make the disambiguation work. 

disambiguation dialog

In the following I will explain how entitity event handlers can be used to implement a desired behavior

The quick win: always override existing values

Lets first look at implementing the quick win, which is that out-of-order extracted values will always override the current values in the bag item. So, if  "green" and "red" were selected and a user typed "yellow" and "blue", the expected outcome is to have the bag item holding "blue" and "yellow" as a value. 

The image below shows the composite bag entity I use in the sample skill that you can download at the end of this article. The composite bag entity has two bag items

  • Color, which is based on a value list entity of the 4 colors green, red, blue and yellow
  • Confirm, a bag item that exects yes or no as an input. However, it allows you to also change the color of the previous bag item

composite bag entity

To start the implementation, I created an entity event handler for the composite bag entity. An entity event handler is a custom component that listens and response to events. One of those events on the item level is the disambiguation prompt message. 

To create an entity event handler, I pressed the + Event Handler button and provide a name for the custom component package and the event handler itself. 

creating a entity event handler

To add an event handler function, I used the + Add Event button in the opened browser based editing environment. I then selected Item-Level Events and then the bag item name (Color in my example).

Selecting the item to create a EEH function for

To create a JavaScript function that handles the disambiguation case, I selected publishDisambiguationMessage and pressed the Insert Event button. 

Handling disambiguation messages

The code snippet below shows the publishDisambiguationMessage function that got created along with the single code line I added to save the out-of-order extracted values as the new bag item values.  

Adding code to always set the user selected values as the new value

Note:  If you are curious about what API calls you can make within entity event handlers, then have a look at the API docs here.

 

The big win: handling add, remove and replace

The quick win will already do it for many use cases. However I wanted to go for the big win, which is to handle all of the use cases mentioned before: add, remove, replace.

As a first step, I created an entity that would be able to extract whether the user wanted to add or remove a color from a user message. This entity is also added as a bag item to the composite bag entity. Unlike the two other bag items, the prompt for value property is set to false so the action bag item is not prompted for. Its used as metadata only. 

Creating an action entity

The image below shows the composte bag entity with the action list entity added as DetectAction bag item. By default the DetectAction bag item has its out-of-order extraction enabled, which means that when a user sees the "All Good?" prompt and types remove green, the action entity will receive "remove" as a value (the DetectAction bag item only expects a single value)

Adding the action entity to the composite bag entity

With the DetectAction bag item in place, the event handler code can be modified to read its value to determine whether the user wants to add colors, remove colors or replace the existing colors. 

const action = context.getItemValue(detectActionBagItem);

The code line shown above reads the value stored in the DetectAction bag item if any. Coding the solution more elegant that the first example, the name "DetectAction" is defined in a variable "detectActionBagItem" on top of the code sample. 

The code logic first checks if the DetectAction bag item got a value. If it does have a value set, it will check wether it is a remove or add action. If no action is provided then the logic will simpy replace the existing values with the new values. 

When remove is detected, then the code accesses the disambiguation color values to look them up among the existin colors helod by the Color bag item. If it finds a match or matches, then those matching colors are removed.

When the add action is detected, then the code checks if the colors in the set of disambigation values are already in the Color bag item list of values or not. If a color is not in there, then it is gets added 

'use strict';
const detectActionBagItem = "DetectAction";
const colorBagItem = "Color";
module.exports = {
  metadata: {
    name: 'Multicolor_EEH',
    eventHandlerType: 'ResolveEntities',
    supportedActions: [] // string array of transition actions that might be set by the event handler
  },
  handlers: {
    entity: {      
      publishMessage: async (event, context) => {
        context.addCandidateMessages();
      }
    },
    items: {
      Color: {

        /**
         * Handler that can be used to replace or extend the bot message generated by ResolveEntities (RE)
         * or Common Response Component (CRC) to disambiguate the values provided by the user. RE/CRC takes
         * the prompt from the disambiguation prompt registered in the edit composite bag item screen. 
         * To use the RE/CRC generated messages, you can call context.addCandidateMessages().
         * 
         * @param {object} event – event object contains the following properties:
         * – disambiguationValues: JSONArray with a list of values for the current item that match the user input 
         * @param {object} context – entity resolution context, see https://oracle.github.io/bots-node-sdk/EntityResolutionContext.html
         */
        publishDisambiguateMessage: async (event, context) => {
          const action = context.getItemValue(detectActionBagItem);
          //read the current selected colors, which are list objects in 
          //an array (because it is a multi value bag item)
          const currentValues = context.getItemValue(colorBagItem);
          //check if disambiguation values should be removed
          if(action != null && action.primaryLanguageValue == "remove"){ 
   
        let colorValueNamesToRemove = [];
            //get the color names from the array of disambiguation values
            event.disambiguationValues.forEach(color => {
              //each color is a value list entity object. The primaryLanguageValue
              //property holds the base language value: blue, green, yellow, red
              colorValueNamesToRemove.push(color.primaryLanguageValue);
            });
            //compare values in the disambiguation message with values in the
            //current values to determine the objects to remove            
            let newValues = currentValues.filter(element => !colorValueNamesToRemove.includes(element.primaryLanguageValue));
            //update the bag item with the remaining values
            context.setItemValue(colorBagItem, newValues);
          }
          //add
          else if(action != null && action.primaryLanguageValue == "add"){
            let colorValueNamesToAdd = [];
            //get the color names from the array of disambiguation values
            currentValues.forEach(color => {
              //As for remove, each color is a value list entity object. ow, red
              colorValueNamesToAdd.push(color.primaryLanguageValue);
            });
            //compare values in the disambiguation message with values in the current 
            //values to determine the objects to add            
            let newValues = event.disambiguationValues.filter(element => !colorValueNamesToAdd.includes(element.primaryLanguageValue));
            
            //add the values that are no yet in the Colors bag item to this bag item. For this an array of objects
            //needs to be added to an array of objects
            let newColorBagItemValues = […currentValues, …newValues];
            //update the bag item with the remaining values
            context.setItemValue(colorBagItem, newColorBagItemValues);
          }
          //replace current values with new
          else{

            context.setItemValue(colorBagItem, event.disambiguationValues);
          }           
        }
      }
    }
  }
};

 

Note: The sample skill has an additional event handler function defined for the Confirm bag item. This function  resets the value of the DetectAction bag item each time the "All Good?" prompt is shown. 

When running the sample skill,  "remove" and "add", or one of the synonyms, can be used to tell the system what to do wth the color names mentioned in the user message. In the image below, "green" should be removed from the prevously extracted values set. The conversation tester also shows you the current state of the cbeSample variable so you can track changes at testing time. 

Removing values

The image below shows the add action. After removing green, yellow and blue are added. This is immediately reflected in the conversation tester cbeSample variable. 

adding values

If you answer yes to the All good? prompt, then the colors are printed in a bit response. 

print the selected values

The image below shows the implemented behavior if no action is contained in the user message. In this case, the out-of-order extracted values will repace the existing values as explained in the quick win example. 

The "replace usecase"

Download Sample Skill

You can download the sample skill for this article from the link below. 

Oracle Digital Assistant Skill (ZIP)

Author