15 Components

Components give your bot its actual functionality. 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 bot 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 bot: security, parsing the user input, routing the dialog flow based on that user input, and outputting the bot’s responses in various ways. To find out when and how to use these components, see Built-In Components: Properties, Transitions, and Usage.

  • Custom components—If your bot 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.

    As noted in The Dialog Flow Structure, the state nodes in your dialog flow definition are built around the components. 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 bot 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 described in Built-In Components: Properties, Transitions, and Usage. 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 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 download the SDK from GitHub.

These instructions create a component package that you can use when you deploy to the ODA-hosted embedded container, Oracle Mobile Cloud, 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, then the runtime doesn't reroute the flow or re-execute the component as described above. In a digital assistant, you might end up with a conversation where the digital assistant first displays a routing choice immediately followed by the prompt for the transition state. 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.

  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 ODA-hosted embedded container, Oracle Mobile Cloud, 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 ODA-hosted embedded container, you simply 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 ODA (Bots) SDK version is 18.4.3.

Deploy to Mobile Cloud

To host a custom component package in Oracle Mobile Cloud, 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.

How Do I Add a Custom Component Package to a Skill?

To add a custom component package to a skill, go to the Components tab (This is an image of the Components icon.) and add a service, which creates 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.

  • It allows the skill to invoke the components.

The JSON payload of the call made by the Dialog Engine 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.

Note:

When you build your own custom component package, you can deploy your custom components to Oracle Mobile Cloud, your own Node.js server, or to the ODA-hosted embedded container, the quickest deployment option. Regardless of the deployment strategy you choose, your custom components can run on any of these environments when you use the files and structure described in Implement Custom Components.

Create a Component Service

Configuring a Custom Component Service connects your skill bot 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, 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.

You need to create a service for each custom component package that your skill uses.

Note:

You must configure the services for each version of your skill bot.

Create a Service for the Embedded Container

While you need to provide authentication and backend credentials to configure services for the components that are hosted on AMC or on your own Node.js server, you only need to upload a TGZ of your custom component implementation when you choose the Embedded Container option. 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 gets built and its components get deployed within ODA.

Note:

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 is complete, its platform version displays in place of the awaiting deployment message.

Create a Service for Mobile Cloud

If you opt for Oracle Mobile Cloud as the container for your custom components, you can integrate them with remote services using various connectors controlled by the backend in Oracle Mobile Cloud (Mobile Cloud). This, in turn, can give the components access to the Mobile Cloud 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. Backend Authentication and Connection Info in Developing Applications for Oracle Autonomous Mobile Cloud describes the backend Settings page.

To configure the service for a Mobile Cloud backend:
  1. Enter the unique identifier assigned to the Mobile Cloud 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 GET endpoint of the custom code API. This URL must be appended with /components because it points to the endpoint of the component service.
  3. Choose Use Anonymous Access if the service allows anonymous login. If you choose this option, enter the Anonymous Key, 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 Cloud. (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.

Create a Service for Your Own Node.js Server

Click Other when you host the custom component package on your own Node.js server. For this option, define the following properties:
Option Description
Metadata URL The URL that points to the GET endpoint that returns the list of components.
Username The user name for the service.
Password The service’s password.