16 Backend Integration

You can integrate skills with backend services, such as Oracle Human Capital Management Cloud and Oracle Financials Cloud, through custom components. For example, you might want to create a skill that lets your employees report their travel expenses to Oracle Financials Cloud.

To integrate a skill with a backend service:

  1. Using JavaScript and the Oracle Digital Assistant Node.js SDK, implement a custom component that transfers data to and from the skill using the SDK's metadata and conversation objects. See Task 1: Implement Custom Components.

  2. Deploy the component package to a Digital Assistant embedded container, an Oracle Mobile Hub backend, or a Node.js server. See Task 2: Deploy the Component Package to a Service.

  3. Configure the component service for the skill. See Task 3: Configure a Component Service.

Task 1: Implement Custom Components

To implement custom components, you use the Oracle Digital Assistant Node.js SDK to interface with Digital Assistant's custom component service.

Here's how to implement custom components that you can deploy to the Digital Assistant embedded container, a Mobile Hub backend, or a Node.js server:

  1. Create the package structure.

  2. Build the custom components.

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. If you plan to deploy your component package to Mobile Hub, you might want to take a look at a sample component package that was built for Mobile Hub deployment, which is available from Digital Assistant Samples for Mobile on the Digital Assistant and Mobile Hub Downloads page on OTN.

Step 1: Create the Package Structure

Your custom component package must conform to the required package structure, which implements a custom component service as well as the custom components.

You can create the package structure manually, as shown here, or you can use the SDK's command line interface (CLI) to create the necessary files and directory structure. See https://github.com/oracle/bots-node-sdk/blob/master/bin/CLI.md.
  1. Create the top-level folder.
  2. Add a package.json file to the top-level folder and add 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"
      }
    }
    
  3. In the top-level folder, add a main.js file, which exports the package settings.
  4. In the main.js file, add a components property, which lists the components to add to the component registry.

    Each element in the array can point to a specific component path or to a directory, which is scanned recursively.

    module.exports = {
      components: [
        './components'
      ]
    };
  5. Add an .npmignore file to the top-level folder. This file is used when you export the component package. It must exclude .tgz files from the package. For example: *.tgz.
  6. Create a components folder. This is where you'll put your component JavaScript files. It's the directory that you reference in the components property in main.js.
If you plan to deploy to the embedded container, your package should be compatible with Node.js 8.11.4.

Step 2: Build Custom Components

Here are the steps for building each custom component in your package:

  1. Create the component JavaScript file.

  2. Add the metadata and invoke functions.

  3. Access the backend.

  4. Use the SDK to access request and response payloads.

  5. Ensure that the component works in digital assistants.

Create the Component JavaScript File

You can implement all your components in a single JavaScript file, or implement each component in its own file, as described here.

To start building a component JavaScript file:
  1. 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.
  2. Add a mobile.exports object to encapsulate the code that must be available to the other files in the package.
    
    module.exports = {
    };
Add the metadata and invoke Functions

The required interface for a custom component implementation is:

// interface for a custom component implementation
{
  metadata(): {name: string, properties?: {[name:string]: string}, supportedActions?: string[]};
  invoke(conversation: Conversation, done: () => {}): void;
}
  1. Inside the module.exports object, define the metadata function.

    This function provides the component descriptions that are displayed in the Components tab in the UI and describe how to use the component in the dialog flow. 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 that the component can return, if any.

  2. Inside the module.exports object, define the invoke function.

    You'll use this function to make REST calls, access request data, update response data, and return to the skill.

    It includes two arguments: conversation, which is a reference to the Conversation class in the Digital Assistant Node.js SDK, and done, which is a callback that the component invokes when it has finished processing.

Here’s an example:

'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();
    }
};
Access the Backend

If your custom component needs to access a backend, use the Node.js Request module to do this.

To send a request to a backend service:
  1. If the service requires an Authorization header, add input parameters to the metadata to pass in the necessary information.
    • For Basic Auth, add parameters for user name and password, or for base-64 encoded <username>:<password>.
    • For OAuth2 with Oracle Identity Cloud Service or Oracle Access Management, add a parameter for the access key. The skill can use the System.OAuth2AccountLink component to get this key.
    • For all other authorization types, you have two choices:
      • Add a parameter for the authorization code, and add code to your component to get the access key. The skill can use the System.OAuthAccountLink component to get the authorization code.
      • Obtain the authorization code and access key from your implementation.
  2. Add the Node.js Request module, as shown here:
    var request = require('request');
  3. Inside the invoke function, send a request to the service. For example:
    
        request('<url>', {json: true }, (err, res, body) => {
    
          var weatherResponse = body;
    
          if (res.statusCode == 200) {
            ...
            conversation.transition('<action>');
            done();
          } else if (res.statusCode == 401) { 
            ...
          } else if (res.statusCode == 404) {
            ...
            conversation.transition('<action>');
            done();
          } else {
            ...
            conversation.transition('<action>');
            done();
          }
        });
Use the SDK to Access Request and Response Payloads

Use Conversation instance methods to get the context for the invocation, change variables, and send results back to the dialog engine.

Here's how you use these methods for some common tasks:

How Do I? Do this
Get a component property value. Call conversation.properties() to get an object that contains all the component properties.
const MyCustomComponent = {module.exports = {
   metadata: () => ({
     name: 'greeting',
     properties: {
       "name": {        
         "type": "string",
         "required": true
     }    
  }   
}),
invoke: (conversation, done) => {
  // Get property values
  const { name: _name = '' } = conversation.properties();
  conversation.reply('Hello ${_name}');
  conversation.transition();
  done();
  }
}
Get the value of a context variable. Add a metadata variable to pass in the name of the variable, and then call conversation.variable(<variable name>)to retrieve the value.
const { latitudeVariable } = conversation.properties();
let _latitude = conversation.variable(latitudeVariable);
Get the value of a profile variable. Call conversation.variable('profile.<name>') to retrieve the value.
Set a context variable's value. Add a metadata variable to pass in the name of the variable, and then call conversation.variable(<variable name>, <value>) to set the value.
if (latitudeVariable) {conversation.variable(latitudeVariable, _coordinates.x);}
Use an object to update a variable that's an entity type, such as ADDRESS, and include an entityName property that's set to the entity type.
if (addressVariable){
  let _AddressObject = {};
  _AddressObject['entityName'] = 'ADDRESS';
  _AddressObject['postCode'] = _zip;
  _AddressObject['state'] = _state; 
  conversation.variable(conversation.properties().addressVariable, _addressObject);
}
Send a reply or a message response. Call conversation.reply(<payload>). The payload can be a string, an object, or a MessageModel. You can call this function multiple times. When you call this function, keepTurn is set to false automatically.
Return to the dialog flow. Call transition(<optional state>) followed by done() to return to the dialog flow and transition to another state. Use transition() to transition to the next state in the flow. Otherwise, use transition(<action>), where <action> is one of the supportedActions in the metadata.
module.exports = {
  metadata: () => ({
    name: 'verify',
    properties: {
      "address": {
        "type": "string",
        "required": false
      }
    },
    supportedActions: ['valid', 'invalid']
  }),
  invoke: (conversation, done) => {
    ...
    conversation.transition('valid');
    done();
    ...
}};

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"

Task 2: Deploy the Component Package to a Service

You can host the custom component package from a Digital Assistant embedded container, Mobile Hub, or a Node.js server.

If you are going to host your component package from an embedded container, then all you need to do is prepare the component package that you'll need to create the component service. When you later create the component service, and choose the Embedded Container option, Digital Assistant hosts the component package automatically. If you don't use an embedded container, then you need to deploy the package to Mobile Hub or a Node.js server.

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.

Prepare the Package for an Embedded Container Service

The easiest way to host your component package is through a Digital Assistant embedded container service. To prepare the package for this service, run npm pack from the directory that contains the package.json and main.js files. You'll 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 Task 1: 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.

Task 3: Configure a Component Service

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

A component service has two functions:

  • It queries the component to get the package metadata, including the names of the components, their properties, and allowed actions for 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, which you will need to use the components in 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.

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 Task 1: 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 Hub backend and they have access to the Mobile Hub platform APIs.

Because the 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 user name 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 hosted 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.