article by Frank Nimphius, June 2020
Like built-in components, custom components may have an interaction with a user that spans multiple turns. To have a conversation with a user, components needs to maintain state to tell the UI to render and the behavior to execute. In this article I explain two options you can use to manage and maintain state in custom components.
Tickets4Sale Sample
The sample use case is an incomplete implementation of an event service bot. As a disclaimer, notice that the main objective of the sample is to provide a framework for demonstrating component state management.
The sample skill in the screen shots uses the following three band names to provide its services: Rolling Stones, Foreigner and Santana (you can add more bands by editing the artists entity of the skill). When the user asks for one of the three bands, a message with randomly generated data is displayed when the band can be seen live on stage. The user can then select to buy a ticket (not implemented in the sample), to get a reminder later via email, or to cancel the ticket query/order process.
So when you send a message "When will the Stones come to town?", the skill will answer with tow dates. Don't become too excited about this because this is not real. The dates – as mentioned – are randomly generated or demonstration purpose. Beside of the event message prompt, the skill displays three buttons for the user to select an action. Though its the initial request, already the component starts managing its state. Reason is that the component now is awaiting the user response, which would be a second turn in the component-user conversation.
State managed by the component: In the screenshot above, the custom component understood the request to be an initial request. So it produced the response along with three buttons to select from.
Shown in the image below, the user did not press a button but sends another message asking about "Santana". As you would expect, the component allows the skill's intent engine to handle the request and doesn't handle the request itself. After all, all event requests are supposed to use the System.Intent engine for intent resolution and entity extraction.
State managed by the component: In the image shot below, the component understood that the request was not an initial request. However, as to this point the component did not expect a user message but a button to be selected, it does not handle the user message and instead passes it back on to the dialog flow for the bot developer to handle. The bot developer handled it in that the user message got routed to the System.Intent component state for a new conversation to start.
Next, the user selects the "Remind me later" button. In response, the custom component displays a prompt (see image below) for the user to enter a mail address to send a reminder to.
State managed by the component: In the image below the component understood that the button pressed by the user belonged to the custom component and that it needed to handle it. In addition, after displaying the prompt for the user to provide a mail address, the component now expects a user text message, which means the message will be handled by the component and passed to the dialog flow.
State Management
State management depends on the component and the task at hand. If the component only prints a message, then it does not need to maintain state at all. If the component goes into a conversation with a user during which it may collect information or even query information from remote, it needs to save the state and information somehow or somewhere. Generally speaking you have to options fro managing state: using button payloads and using variables you create from within the component.
State management using a payload:
Action buttons, especially postback actions, return a payload to the skill, which then gets passed to the component referenced in a current dialog flow state. Postback actions are defined by the custom component developer. They are defined as JSON objects and returned as such. Usually JSON payloads would contain variable names and values to update the named variables with, or actions to return in an action transition to trigger navigation to a next state. However, there is no restriction in what a postback payload can be used for. So, to manage state, a payload contains tokens that identify the origin of a button as well as the button itself so the custom component can determine the code logic to execute.
State management using variable(s):
Custom components can create dialog flow variables to save values and state so they become available across component invocation. If the message sent to the component is not a postback action but a text message, an attachment or a location, then there is no payload you can customize to hold tokens. In this case you can use conversation.variable(name, value) to create a hidden dialog flow variable. The variable is not visible in the dialog flow, which means it cannot be accessed using Apache FreeMarker expressions at design time. Also, the variable needs to be managed by the component, which means that its value needs to be set back to null when the component triggers navigation to another state. The variable can hold primitive values (string, int, float, boolean, array) as well as complex objects. Of course, user scope variables too could be created. However, if you do so then the use case should require information to persist beyond user-bot conversation sessions. Keep in mind that user scope variables are not a replacement for a backend database.
The TicketsForSale custom component exposes the eventInfo property for the bot developer to pass the message to be displayed as a prompt. Important to note is the cancel and the textReceived transitions. Both transitions are triggered by the component. The textReceived transition is triggered when the component doesn't expect user text messages but receives one. It gives the user a chance to change her mind and the bot developer to route the message to a System.Intent state.
The image below shows 5 tokens defined in the Tickets4Sale custom component. The tokens values are not security relevant but used this way to ensure unique names. As a custom component developer, you have no guarantee that the component you build will be the only component that is used in a skill. So the more random a state identifier is, the more likely it is that there wont be conflicts with other components. So don't mind the ugliness of a value if the variable you assign it to is descriptive.
The componentStateVariable variable holds the name of the internal variable the component creates. In the example the variable holds three possible values true, false, null. The component uses the three information to tell whether its the initial time that it renders and whether or not it expects user free text input. If more complex state management is required, you can save a complex object in the variable.
The componentToken variable holds the value of the component token that gets passed to the bot as part of the button payload. An action button, when pressed, sends a JSON payload to the component. The content of this payload is completely up to the custom component developer. It can contain hints about variables to update as well as the values to update a variable with, actions, and, as in this case, tokens. So whenever a user presses a button, the component token is sent to the bot. The component token identifies button actions to have their origin in the custom component.
When handling postback actions, the custom component checks the component token to determine whether the button belongs to this custom component or if the postback was sent by another component (e.g. a Common Response component) to trigger navigation. If the component token matches the token in the custom component, then the code logic looks into the content of the payload to determine the button that was pressed (see image below). The "button" property is a custom attribute added to each button's payload as you will see later in this article.
The component has three more variables set with tokens: buyButtonId, cancelButtonId, and remindMeButtonId. So instead of only sending actions and variable states, the button has an ID. The ID is used in the sample to determine the code logic the component should execute. For example, if the button Id is remindMeButtonId then the component would render a prompt for the user to provide a mail address to send a reminder to. At the same time, the component would set its internal variable state to await user free text to be entered.
How to determine the type of message that was sent? The Oracle custom component SDK provides the following functions to determine message types: conversation.postback(), conversation.text(), conversation.attachment(), conversation.location(). Each function returns null if the incoming message does not match the message type the function represents. Otherwise a payload is returned. The payload in the case of a text message is a string. Its a JSON object otherwise.
Note: If you fancy parsing the message yourself for handling message types and payloads, then you can call conversation.rawPayload().
The tickets for sale component performs the following check for incoming messages if the message is not postback:
- If internal variable is not set or null then its the first time the component is called. In this case the component ignores the text message.
- If the internal variable is set and if it is set to false then the component does not expect user text input. Any text message that cannot be mapped to a postback label will be returned to the dialog flow as an textReceived action
- If the internal variable is set and if it is set to true then the component knows that this is for the mail address. In this case, conversation.text() returns the user message. Its up to the component to validate the user message for a valid email format and to do whatever is needed to set a reminder.
Notice how the code first checks for whether the message is of type text message to then check the internal variable for whether or not text was expected. If text is not expected, then the textReceived action is triggered. This then allows the bot developer to navigate to e.g. a System.Intent state to handle the user text message. Also notice how the variable value is set to null when the text was expected and when it gets handled by the component.
Note: There is no API you can use to delete an internal variable you created. The component is reset when a conversation ends with a return state. So all housekeeping you can do in a component is to set its value to null.
The component UI is rendered when the message is neither a valid postback message or valid text message. In the image below you can also see how the postback payload gets added to the buttons. Each button gets a "token" property set to the component token, a "button" property set to the button Id. You could extend the payload object with your own properties as you please.
To create a button to be added to the component response, you call: conversation.MessageModel().postbackActionObject(label, icon, payload_object, keywords[], skipAutonumbering)
Downloads
Custom component (ZIP) (run npm install from the root folder of the unzipped package)
Related Content
Custom component development and backend integration hands on tutorial
TechExchange Quick-Tip: Accessing Remote Rest Services from Custom Components Using The Node.js HTTPS module
TechExchange: How-to Build Card Layout Responses from Custom Components