Custom Code and Backend Integration

Here some best practices for writing custom code and doing backend integration for digital assistants.

At the end of a conversation, you need to do something with the information collected from a user. That "something" usually requires access to a backend service for querying data or persistent data for which you need to create custom components. Another use for custom components is to incorporate custom logic that handles complex validations or other utility functions. Oracle Digital Assistant supports two types of custom components:

  • Custom dialog flow components (CCS)
  • Entity event handlers (EEH)

During the planning and design phase of your digital assistant, you need to identify the backend resources you will need and decide whether the APIs you have available for it are sufficient or not.

  • Because digital assistants are not web applications, existing APIs may need to be optimized or abstracted through an optimization layer to return only the data and the amount of data required in a digital assistant conversation.
  • If you don't have REST services for the backend functionality to integrate into a digital assistant conversation, then you need to design and trigger a project to build them.

When implementing your backend service integration, you make decisions about whether to deploy custom components remotely or to use the embedded component containers in Oracle Digital Assistant skills.

As the figure below indicates, backend integration is a necessary part of the planning and the implementation phase.


Description of implement-backend-integration.png follows

Custom Dialog Flow Components

With custom dialog flow components, you can write your own user interface components that you can add to your dialog flow to execute custom code logic in the context of a conversation. Use cases for writing those components include:

  • Querying and writing of remote backend services via REST services.

  • Out-of-the-box solutions that handle all user interactions for a specific task like requesting user feedback at the end of a conversation, logging and reporting errors to an administrator, etc.

  • Support managing data in object arrays saved to a dialog flow variable.

Use Good Names for Components and Input Parameters

There isn’t a field for providing descriptions for custom components that explain what they do and what information must be passed to them. So the best ways to help skill developers with use of your component are to use good names for the component and input parameters and to carefully choose the action strings that your component returns.

  • Built-in YAML components use System.<name> as their name. So, especially for YAML-based dialog flows, you may want to use Custom.<name> for skill reviewers to understand it’s a custom component that the dialog flow references. Or, you can use a name space to provide context. For our sample custom components we often use oracle.sample.<name> to indicate that those components are not meant to be production quality.

  • Input parameters provide data to the custom component to process. Often the data passed to a custom component is not the actual value to work with, but rather the name of a variable that either holds the data values to process or to which data queried from a remote service should be written to. Looking at the built-in components, they use variable as the property name to hold the name of the variable that the component result will be written to, or <name>Var (e.g. nlpResultVar) to indicate properties that refer to a variable reference name. You can further improve this by using the _in and _out postfixes to indicate whether a variable refers to a variable that contains data or is expecting data from the component.

  • Action strings are optional and can be used by the skill developer to determine the next dialog flow state to navigate to. Using success or failure as an action string does not provide much context, so we suggest using something like orderSubmitted, orderRejected, or userUnauthorized instead.

Avoid Making Assumptions in Your Code

The reality is that often the developer of a custom component is also the skill developer who uses it. For this reason, many developers simplify their work with custom components by making assumptions about variables that are present in the skill. So instead of passing the name of a variable to the component, they refer directly to the name in the custom component logic. We do not recommend this because such assumptions can easily break a custom component. We recommend defining a clear and complete contract between the custom component and the skills using it.

Think Library

A common question is about how many custom components should be added to a custom component service package. In general it is always a good idea to think of custom component services, and the components it contains, as libraries. So, all components that relate to a task could be saved in a single custom component service. However, recommendations need to be able to face reality. Therefore, the question about how to package custom components needs to be answered based on the intended use of the custom components.

  • Reuse is not an option for many custom component developers. When custom components are developed for and used in a specific skill, it makes sense to group all those components into a single custom component service deployment. The exception to this is for components that are actually reused in other skills.

  • Embedded component container deployments are restricted by the number of custom component services per Oracle Digital Assistant instance. Therefore, you’ll want to use a single custom component service per skill or look for a remote deployment of your custom components.

  • Use remote custom component service deployment to Kubernetes in Oracle Cloud Infrastructure, for the following reasons:

    • To not disclose sensitive information contained in your custom component code. Custom component services deployed to the embedded container can be downloaded by anyone who has full access to your Oracle Digital Assistant instance.

    • To implement better segmentation of your code. Custom components should only contain code that is necessary to interact with the bot and to invoke REST services. All other code should be stored in external JavaScript files (if using an embedded container) or in integration layers (REST services). Custom components include code that does the following things:

      • read input parameters

      • read/set variable values

      • handle messages received by a component

      • render the custom component user interface

      • determine transition to a next state

      • manage component state

      • access REST services

    • To improve performance. The embedded container for deploying custom components to skills uses OCI functions, which have a cold start delay. To avoid this delay, as well as the limit in the number of services that can be deployed, a remote deployment of custom component provides you with a worry-free alternative.

    • To share common components. Though our experience is that reuse isn’t highly ranked among custom component developers, it makes sense to create and deploy commonly used custom components to a remote server. You might have common components for things like error handling and escalation, 2-legged OAuth2 authorization handling, and more.

How to Write Log Messages

The default logger implemented for custom components is the console logger. You access the logger through a call to context.logger(). You can the call logging functions available for the console logger like ".info('…') or ".warn('…')".

Note: Using context.logger() makes the most sense when deploying to the embedded container, as the embedded container knows how to properly display these logs. For custom components that you deploy externally, it is best to use a different logging library, like log4js.

Manage Your Component's Internal State

Custom dialog flow components may have a longer interaction with a user before navigation transitions to a next dialog flow state. For this interaction you need to make sure the component handles its internal state so it can distinguish between an initial call and subsequent calls. There are two options for doing so:

  • Add a token to the postback message payload. When the custom component renders a user interface where users can press an action item, a postback message is sent back to the custom component. The custom component must evaluate the postback messages it receives to determine whether that postback is from the user interface it is rendering or from another component. For this it can check the postback message as to whether it contains a token that the custom component added when rendering the action item.

  • Use a context variable. If you need to manage a more complex state between custom component invocations, e.g. to keep track of values extracted from user messages, you can use a dialog flow variable for this. Custom components can create dialog flow variables at runtime in a call to context.variable('variable name', value). If a variable of the specified name does not exist, then it will be created. The "value" object can be anything you need to keep track of.

Validate Input Parameters

Input parameters that you define for a custom component need to be validated to have content. This is also true for parameters that you set as required. The following cases need to be checked:

  • The input parameter has a value set.

  • The value does not start with '$ {', as this indicates an expression for reading the input parameter value from a variable or that an object is not correctly resolved in the dialog flow.

Use the MessageFactory Class for Component Messages

All bot responses you send from a custom component should use the MessageFactory class. The MessageFactory class can be used to create the same type of rich user messages as the Common Response component, which includes list of values, attachments, card layouts and text messages.

In addition, messages that are defined with the MessageFactory class are channel independent. This means that you create a single component message, which is then converted by the channel-specific connectors into the format required by the respective client channel.

To access the MessageFactory class from a custom component, you use the following reference:

let MessageFactory = context.MessageFactory();
Note

The MessageFactory class supercedes the MessageModel class, which has been deprecated. Both classes have the same general purpose, but MessageFactory has the following advantages:
  • It supports all Common Message Model (CMM) message types and properties, instead of just a subset.
  • Its implementation is class based, providing clean getter, setter, and add methods to change the message definition. Code completion is when using Typescript as well as in JavaScript when the proper type definitions are included at the top of the event handler or custom component.
  • The implementation uses the builder pattern, which allows you to chain a number of setter or add methods, making the code more readable and reducing the number of lines you have to code

Checklist for Custom Components

  • ☑ Ensure backend services are optimized or abstracted for use with skills.
  • ☑ Custom components should only contain bot-related code. All other code should be moved into utility classes or libraries, or be deployed as individual REST services to a remote server or cloud service.
  • ☑ Create a clear and complete contract between custom components and the skills they are used in.
  • ☑ Use custom components for complex evaluations. Avoid Apache FreeMarker in those cases.
  • ☑ Manage component state for multi-request user interactions.
  • ☑ Validate all custom component input parameters.
  • ☑ Handle errors by returning an action string for the skill developer to handle problems.

Entity Event Handlers

An entity event handler is a type of custom component that allows you to invoke custom component code in the context of resolving composite bag entities. Entity event handlers are used in model-driven conversations to interact with and validate user input and to invoke remote backend services for read and write access. Unlike custom dialog flow components, the chance of reuse is minimal for an event handler, which is why the default implementation of entity event handler is to the embedded skill container.

Add Missing Functionality to Resolve Entities Components

A lot of the functionality that can be set for the Common Response component, like global buttons for help and cancel, is not available to the Resolve Entities component through configuration.

However, you can add missing functionality using entity event handlers. This enables you to take advantage of the simplicity of the Resolve Entities component in the dialog flow without sacrificing advanced functionality.

Manage State

Entity event handler functions are invoked by the Resolve Entities and Common Response components when resolving a composite bag entity. There is no need for you to track which bag item needs to be resolved next as it is all done for you.

Still, you may want to save some information for later use. For this you have two options:

  • Context resolution properties are variables you create on the context object. The variables and their values exist until the composite bag entity is resolved or you leave the dialog flow state that resolves a composite bag entity. The benefit of using the context resolution properties is that there is no housekeeping you need to do.

    • For write, use: context.setCustomProperty(name, value);
    • For read, use: context.getCustomProperty(name);
  • Dialog flow variables created at runtime or at design time can be used to store values you want to persist beyond the resolving of the composite bag entity. Content stored in dialog flow variables can be accessed from dialog flow states (for variables defined at design time only) and from other entity event handlers.

    • For write, use: context.variable(name,value);
    • For read, use: context.variable(name);

How to Write Log Messages

The default logger implemented for entity event handlers is the console logger.

You access the logger through a call to context.logger().

You can the call logging functions available for the console logger like .info('…') or .warn('…').

Displaying User Messages

Custom user messages are displayed through the context.addMessage() function. As with custom dialog flow components, our recommendation is to use the MessageFactory class for creating channel-agnostic messages instead of outputting channel-specific payloads. Entity event handlers also support messages of type value-list, card layout, and attachment.

Checklist for Entity Event Handlers

  • ☑ Store temporary values in the resolution context unless needed in a later dialog flow state.
  • ☑ Use a single custom component service for all entity event handlers used in a skill.
  • ☑ Use the MessageFactory class for messages to display to users.

Which Component Should You Use?

Entity event handlers are for use with composite bag entities, while custom dialog flow components are used in the context of conversations transitioning between dialog flow states. Ultimately, you'll probably be using both. If you are following the recommendation to use model-driven conversations, you will be more likely to use entity event handlers than custom dialog flow components.

From a functional point of view, custom dialog flow components (CCS) and Entity Event Handlers (EEH) are very similar. The table below compares the two custom component types.

Functionality CCS EEH
Node.js module support / JavaScript development Yes Yes
TypeScript support Yes Yes
Browser-based development No Yes
Development in external IDE Yes Yes
Use in dialog flows Yes No
Use in composite bag entities No Yes
Input parameters Yes No
Programmatic navigation to action transitions Yes No
Call REST services Yes Yes
Read from / write to dialog flow variables Yes Yes
Store values temporarily in resolution context No Yes
Use resource bundles / multi-language support Yes Yes
Render rich user interfaces and prompts to interact with users Yes Yes
Skill container deployment support Yes Yes
Remote deployment support Yes Yes
Local debugging support (requires NGROK or other tunnels) Yes Yes
Custom events No Yes
Post back action support Yes Yes

Using Resource Bundles for CCS and EEH

Custom dialog flow components and entity event handlers that display bot messages to the user must display messages in the languages supported by the digital assistant.

Until recently, there was no easy way to use resource bundles that were defined in a skill from custom components. But now there’s a new programming interface that allows you to refer resource bundle keys in your code. There are two known restrictions you should be aware of:

  • Use of resource bundle strings is limited to resource bundles without parameters or with positional parameters. Named parameters as used with ICU message bundles are not yet supported by the new API.

  • The API produces an expression that, when returned as a bot response gets replaced with the referenced message bundle string for the detected language.

To call the new API, you use one of the following context object calls:

  • let expression = context.translate('resource_bundle_key_name');
  • let expression = context.translate('resource_bundle_key_name', param1, param2);

The expression can be used in text responses, as button labels, and on cards using the MessageFactory class.

  • Entity event handler sample:

    const messageModel = context.getMessageFactory();
    //create a conversation message format text object that references a key name
    const message = messageModel.createTextMessage(context.translate('resource_bundle_key'));
    //display the message to the user keeping the turn, which means the composite bag entity
    //proceeds with the next bag item to resolve
    context.addMessage(message,true);
  • Custom dialog flow example:

    const messageModel = context.getMessageFactory();
    //create a conversation message format text object that references a key name
    const message = messageModel.createTextMessage(context.translate('resource_bundle_key'));
    //display the message to the user keeping the turn, which means the composite bag entity
    //proceeds with the next bag item to resolve
    context.reply(message); context.keepTurn(true);
    context.transition(); done();

Also see the TechExchange article Use input parameters to pass translated resource bundle strings to custom components.

How to Use Named Parameters

Here's how you can access named parameters in a resource bundle:

//Entity event handler sample
let expression = "${rb('key_name','param_name1,param_name2',"+value1+","+value2+")}";
let message = messageFactory.createTextMessage(expression);
context.addMessage(message,true);
//Custom dialog flow component sample
let expression = "${rb('key_name','param_name1,param_name2',"+value1+","+value2+")}";
let message = messageFactory.createTextMessage(expression);
context.reply(message);

Our Recommendation Regarding Resource Bundles and Custom Components

Using resource bundles everywhere is a common theme throughout this guide. However, the use of resource bundles stored in a skill creates a tight coupling between the custom dialog flow component or event handler and the skill. If you're okay with this dependency and value the benefit of having resource strings managed in a single place more than avoiding the problem of tight coupling, you should do it. For event handlers, the chance of reuse is minimal anyway, which is why there should be no doubt about the use of resource bundle strings in entity event handlers at all.

For custom dialog flow components that are reused in different skills, the translation function will also work if the skills have the resource bundle key names required by the custom component added to their resource bundle.

Using an alternative solution, you can avoid tight coupling of custom components to a skill by passing the messages read from a resource bundle as input parameters to a custom dialog flow component.

Should You Migrate to Entity Event Handlers?

If you move from custom dialog flow components to entity event handlers, it should be for a specific reason, not just because it is a new technology. Changing likes by likes does not improve your skills. If you are unhappy with your current conversation flow and are considering using composite bag entities to replace parts of your dialog flow conversation, that’s one good reason to move the code logic from custom dialog flow components to entity event handlers.

Best Practices When Migrating to Entity Event Handlers

If you decide to move existing functionality from custom dialog flow components to entity event handlers to improve your conversation flow, make sure that you are not just trying to mimic the behavior that you implemented with the custom dialog flow component and the Common Response component. Instead, start using the Resolve Entities component and use entity event handler functions to implement all of the validation and logic required for your conversational use case.