15 Components

Components give your skill its actual functionality. The state nodes in your dialog flow definition are built around them. These reusable units of work perform all manner of tasks and functions, from the basic management of the dialog flow to case-specific actions.

There are two types of components: built-in components and custom components. The type of component that you use depends on the kind of action that you need your skill to perform.
  • Built-in components: We provide a set of built-in components that support a range of generic actions that you can use in any skill: security, parsing the user input, routing the dialog flow based on that user input, and outputting the skill’s responses in various ways.

  • Custom components: If your skill calls for a specific action that’s outside of the functions provided by the built-in components, such a returning backend data or implementing business logic, then you’ll need to use a custom component. These components are specific to your use case, so you need to provide them.

You use the same approach for defining states for both the built-in components and custom components: you name the component and then define its properties. However, there’s a key difference for custom components: the built-in components will technically function if your dialog flow is syntactically correct; the custom components can’t function until you’ve defined a way for your skill to access the custom component implementation, namely through a REST service known as a Custom Component Service. When the Dialog Engine enters a state in the dialog flow, it assesses the component. When it encounters one of the built-in components (noted by System.), it executes one of the generic tasks, such as display a message or prompt the user to enter text. When the Dialog Engine discovers a custom component, however, it calls the Custom Component Service, which hosts one or more custom components.

Implement Custom Components

To implement custom components, you create a JavaScript component package that has the following structure and contents. You use the Oracle Digital Assistant SDK to interface with Digital Assistant's custom component service. This SDK is available from https://github.com/oracle/bots-node-sdk.

You can find up-to-date examples, as well as more detailed instructions at https://github.com/oracle/bots-node-sdk/tree/master/examples/custom-components/starter/custom. You also can get a sample custom component package from Digital Assistant Samples for Mobile, which is available from the Digital Assistant and Mobile Hub Downloads page on OTN.

These instructions create a component package that you can use when you deploy to the Digital Assistant embedded container, Mobile Hub, or a Node.js server.

If you plan to deploy to the embedded container, your package should be compatible with Nodejs 8.11.4.

  • package.json: This package file must have a main entry that specifies the entry point, which is main.js. To assist in the development and testing processes, name @oracle/bots-node-sdk with its version as a devDependency .
    {
      "name": "my-custom-component-pkg",
      "version": "1.0.0",
      "main": "main.js",
      "dependencies": {},
      "devDependencies": {
        "@oracle/bots-node-sdk": "^2.1.0"
      }
    }
    
  • main.js: This code exports the package settings. It must contain the components property, which lists the component paths as shown here:
    module.exports = {
      components: [
        './components/hello.world'
      ]
    };
    
    If all of the component .js files are in the same folder, you can use this alternative:
    module.exports = {
      components: [
        './components'
      ]
    };
    
  • .npmignore: This file is used when exporting the component package. It must exclude the .tgz file from the package. For example: *.tgz

  • components: This directory is the location for all of the component JavaScript files. It is the directory that you reference in the components property in main.js

Tip:

The SDK includes a command line interface (CLI) that you can use to create the necessary files and directory structure. See https://github.com/oracle/bots-node-sdk/blob/master/bin/CLI.md.

Create Component Files

To create a component, you create a .js file and give it the same name as your component. You can put component .js files in any package folder, but it’s easiest if you put all of them in the same folder, such as components.

Tip:

The SDK includes a command line interface (CLI) that you can use to add component .js files to a package. See https://github.com/oracle/bots-node-sdk/blob/master/bin/CLI.md.
Add Required Functions

A component .js file must include the following functions. You also need to conclude the module with the callback function, done.

  • metadata: This function provides the component descriptions that are displayed in the Components tab. They provide information to help design dialog flows. The metadata includes a component name (which must be unique), and the names and types of the input parameters that it expects. It also includes the actions supported by the component, if any.

  • invoke: This function executes the REST call. It includes two arguments: conversation, which is a reference to the Conversation class in the Oracle Bots Node.js SDK, and done, which is a callback that the component invokes when it has finished processing.

    Always include the done() callback at the end of each component. The component can’t send its response without it and, as a result, the skill times out.

Here’s an example of how you use these functions:

'use strict';

module.exports = {
    metadata: () => ({
        name: 'hello.world',
        properties: {
            human: {
                required: true,
                type: 'string'
            },
        },
        supportedActions: []
    }),
    invoke: (conversation, done) => {
        // Perform conversation tasks.
        const { human } = conversation.properties();
        conversation
            .reply(`Greetings ${human}.`)
            .reply(`Today is ${new Date().toDateString()}.`)
            .transition();

        done();
    }
};
Use the SDK to Access Request and Response Payloads

You use the Conversation class’s methods to get the context for the invocation, change variables, and send results back to the dialog engine.

In our example, the invoke function uses the conversation.properties method to retrieve the value of the human variable from the payload of the POST request.

The full SDK documentation is at https://github.com/oracle/bots-node-sdk.

Ensure the Component Works in Digital Assistants

In a digital assistant conversation, a user can break a conversation flow by changing the subject. For example, if a user starts a flow to make a purchase, they might interrupt that flow to ask how much credit they have on a gift card. We call this a non sequitur. To enable the digital assistant to identify and handle non sequiturs, call the conversation.invalidInput(payload) method when a user utterance response is not understood in the context of the component.

In a digital conversation, the runtime determines if an invalid input is a non sequitur by searching for response matches in all skills. If it finds matches, it reroutes the flow. If not, it displays the message, if provided, prompts the user for input, and then executes the component again. The new input is passed to the component in the text property.

In a standalone skill conversation, the runtime displays the message, if provided, prompts the user for input, and then executes the component again. The new input is passed to the component in the text property.

This example code calls conversation.invalidInput(payload) whenever the input doesn’t convert to a number.

"use strict"
 
module.exports = {
 
    metadata: () => ({
        "name": "AgeChecker",
        "properties": {
            "minAge": { "type": "integer", "required": true }
        },
        "supportedActions": [
            "allow",
            "block"
        ]
    }),
 
    invoke: (conversation, done) => {
        // Parse a number out of the incoming message
        const text = conversation.text();
        var age = 0;
        if (text){
          const matches = text.match(/\d+/);
          if (matches) {
              age = matches[0];
          } else {
              conversation.invalidUserInput("Age input not understood. Please try again");
              done();
              return;
          }
        } else {
          var errText = "No age input provided";
          conversation.logger().error(errText);
          done(new Error(errText));
          return;
        }
 
        conversation.logger().info('AgeChecker: using age=' + age);
 
        // Set action based on age check
        let minAge = conversation.properties().minAge || 18;
        conversation.transition( age >= minAge ? 'allow' : 'block' );
 
        done();
    }
};

Here’s an example of how a digital assistant handles invalid input at runtime. For the first age response (twentyfive), there are no matches in any skills registered with the digital assistant so the conversation displays the specified conversation.invalidUserInput message. In the second age response (send money), the digital assistant finds a match so it asks if it should reroute to that flow.


Description of components-nonsequitur-conversation.png follows
Description of the illustration components-nonsequitur-conversation.png

You should call either conversation.invalidInput() or conversation.transition(). If you call both operations, ensure that the system.invalidUserInput variable is still set if any additional message is sent. Also note that user input components such as System.CommonResponse, System.Text, System.List, and System.ResolveEntities reset system.invalidUserInput.

Say, for example, that we modify the AgeChecker component as shown below, and call conversation.transition() after conversation.invalidInput().

if (matches) {  age = matches[0]; } else { 
      conversation.invalidUserInput("Age input not understood. Please try again"); 
      conversation.transition("invalid"); 
      conversation.keepTurn(true);
      done();
      return;
}

In this case, the data flow needs to transition back to askage so that the user gets two output messages – "Age input not understood. Please try again" followed by "How old are you?".

  askage:
    component: "System.Output"
    properties:
      text: "How old are you?"
    transitions: {}
  checkage:
    component: "AgeChecker"
    properties:
      minAge: 18
    transitions:
      actions:
        allow: "crust"
        block: "underage"
        invalid: "askage"

Deploy the Component Package

After you build a component package, you can deploy it to the Digital Assistant embedded container, Mobile Hub, or a Node.js server. The deployment steps vary for each type of host.

Tip:

The SDK includes a command line interface (CLI) that you can use to package the files and directories for the target service. See https://github.com/oracle/bots-node-sdk/blob/master/bin/CLI.md.

Deploy to the Embedded Container

To deploy your component package to the Digital Assistant embedded container, you run npm pack from the directory that contains the package.json and main.js files, and then upload the TGZ file when you create a service for the embedded container from the skill’s Components tab.

Note:

The Nodejs version for the embedded container is 8.11.4. The Digital Assistant (Bots) SDK version is 18.4.3.

Deploy to Mobile Hub

To host a custom component package in Mobile Hub, you basically create a custom API with a few changes that are specific to custom component packages, and then upload a ZIP of the component package into the custom API.

  1. Create a .raml file with the following contents. Change <custom-component-package-name> to the name of your component package.

    #%RAML 0.8
    title: <custom-component-package-name>
    version: 1.0
    baseUri: /mobile/custom/<custom-component-package-name>
    protocols: [HTTPS]
    /components:
      get:
      /{componentName}:
        uriParameters:
          componentName:
            displayName: componentName
            type: string
        post:
          body:
            application/json:
  2. From the AMC APIs page, click New API > API, and create the custom API by uploading the .raml file.

  3. From the Security tab, switch off Login Required.

  4. From the Implementation tab, download the implementation JavaScript scaffold (a ZIP file) that’s generated by AMC, and extract the contents to create your component package folder and files.

  5. In the root folder of your component package, run this command to install the SDK into the project.

    npm install --save @oracle/bots-node-sdk
  6. Build your component package as described in Implement Custom Components with the following exceptions:

    • Use the main file from your downloaded implementation instead of the main.js example. You can rename it to main.js (and update the main property in package.json to match) if you want.

    • Use the package.json file from your downloaded implementation instead of the one from the sample. Add a devDependency for the SDK:

      "devDependencies": {
        "@oracle/bots-node-sdk": "^2.0.0"
      }
      
  7. Open the main file and replace the contents with this code. Change <custom-component-package-name> to the name of your custom API.

    /**
     * The ExpressJS namespace.
     * @external ExpressApplicationObject
     * @see {@link http://expressjs.com/4x/api.html#app}
     */
    
    // IMPORTANT: Please customize apiURL it for you needs.
    // apiURL is the AMC API URL that implements the custom component service GET,
    // in the format '/mobile/custom/[API_NAME]/components'
    const apiURL = '/mobile/custom/<custom-component-package-name>/components';
    
    const OracleBot = require("@oracle/bots-node-sdk");
    
    /**
     * Mobile Cloud custom code service entry point.
     * @param {external:ExpressApplicationObject}
     * service
     */
    module.exports = function (service) {
    
      OracleBot.Middleware.customComponent(service, {
        baseUrl: apiURL,
        cwd: __dirname,
        register: [
          './components'
        ]
      });
    
    };
  8. Zip up the component package and upload it from the custom API’s Implementation tab.

  9. From the Test page, invoke the GET request. The response should show the component metadata.

    Tip:

    If you get a status 500, and the error is that it can’t find a matching route definition, check your files for bad JavaScript syntax, as that is the typical cause.

Deploy to a Node.JS Server

To host a custom component package on an external Node.js server, you wrap your custom component package in a Node.js Express service wrapper.

  1. Create a folder for the service wrapper, add the top level files from https://github.com/oracle/bots-node-sdk/tree/master/examples/custom-components/starter, and edit the files as necessary for the service. Note that you can also download the SDK and copy the files from there.

  2. Put your component package in the service wrapper folder.

  3. Run these commands:

    npm install
    
    npm start

Tip:

The SDK includes a command line interface (CLI) that you can use to create the service wrapper and start the service. See https://github.com/oracle/bots-node-sdk/blob/master/bin/CLI.md.

Add a Custom Component Package to a Skill

You need to create a service for each custom component package that your skill uses. A service is an active connection from the skill to the components. The service has two functions:

  • It queries the component to get the package metadata, including the names of the components, and the properties and allowed actions of each one. After a service is added to the skill, you can see this information in the Components tab, which you access by clicking Components (This is an image of the Components icon.) in the left navbar. You can reference this page to get the component names, properties and actions when you define your dialog flow.

  • It allows the skill to invoke the components.

    The JSON payload of the call made by the Dialog Engine to the components includes input parameters, variable values, user-level context, and the user’s message text. The component returns the results by changing the values of existing variables or adding new ones (or both). The Dialog Engine parses the returned payload and proceeds.

To add a custom component package to a skill, go to the skill's Components tab (This is an image of the Components icon.) and click Add Service, which opens a dialog for configuring the service.

Configure a Component Service

Configuring a new custom component service connects your skill to the service that hosts the component package. In addition to enabling it to invoke a post call on a component, the service also returns the metadata that displays in the Components page.

How you configure the service depends on where you are hosting the component package – Mobile Hub, your own Node.js server, or the Digital Assistant embedded container, which is the quickest deployment option.

Configure a Service for the Embedded Container

When you choose the Embedded Container option, which deploys the service within Digital Assistant, you only need to upload a TGZ of your custom component implementation. This TGZ file, which you package using npm pack, must contain the assets and structure described in Implement Custom Components.

After you upload the TGZ file, the custom component service is built and its components are deployed. If the Components page displays an awaiting deployment message after you upload the TGZ file, it means that the service has been created, but is not yet available. When the service becomes available, its platform version displays in place of the awaiting deployment message.

Configure a Service for Mobile Hub

If your custom component package is hosted on Oracle Mobile Hub, then choose Oracle Mobile Cloud as the container for your custom components. Custom components that are hosted on Mobile Hub can integrate with remote services using various connectors controlled by a Mobile Hubbackend and have access to the Mobile Hub platform APIs.

Because the mobile backend that hosts the custom code handles the authentication for the custom components, you need to refer to the backend’s Settings page to get the information that you need to complete the configuration.

To configure the service for the Mobile Hub backend:
  1. Enter the unique identifier assigned to the Mobile Hub backend in the Backend ID field. This ID is passed in the REST header of every call from the skill.
  2. In the MetadataURL field, enter the /components endpoint from the custom code API. For example, http://server:port/mobile/custom/ccPackage/components.
  3. Choose Use Anonymous Access if the service allows anonymous login. If you choose this option, enter the anonymous key, which is a unique string that allows your app to access anonymous APIs without sending an encoded username and password. The anonymous key is passed in their place. You can find the anonymous key on the backend's Settings page in Mobile Hub. (You may need to click Show.)

    If the component service requires a login (meaning no anonymous access), then enter the username and password.

  4. If the service requires specific parameters, click Add HTTP Header and then define the key-value pairs for the headers.
  5. Click Create.

Configure a Service for Your Own Node.js Server

Choose the Other option when your custom component package is hosed on your own Node.js server. For this option, enter the The URL that points to the GET endpoint that returns the list of components in the Metadata URL field along with the service's user name and password. Then click Create.