In the earlier stages of LUIS, binding Intents to Actions was something that was automatically handled by the LUIS service behind the scenes. Because of this, developers could only influence action binding via the LUIS app portal. Action Binding has since been deprecated and removed from the LUIS.ai service, and abstracted to the client side, which allows the client (developer) to have direct control over action binding, and ultimately visible control over conversational flow.
With control over action binding being given to the client, we can now also specify requirements for an action to be triggered when bound to an intent. These requirements are defined as action members, and will match recognizable entities for the intent that the action maps to.
Client side action binding supports only one action per intent. Each action includes members (properties) mapping to entities. These action members can be optional or required, the client framework will provide you the tools to validate the action’s state in order to see if it can be fulfilled or not.
Defining an Action in your Application
The new Action Binding library provides a different implementation to bind Intents to Actions, however certain concepts such as defining action parameters by using properties (that would map LUIS recognized entities) remain the same. Because LUIS will no longer determine an action based on a user’s query, actions need to be constructed to accommodate intents and entities. Although the same binding concept is apparent in both C# and NodeJS, the difference in languages as you’d expect, necessitates different implementations.
Moving onto the code, we begin with the C# implementation before moving on to the NodeJS.
Source Code: C# Action Binding Samples
If you are currently working with the Bot Builder Framework for C#, many concepts will be familiar while others will be new.
The new Action Binding library defines an
ILuisAction interface, which provides the contract shown below:
This interface includes two methods, which you define from the client:
- FulfillAsync: Provide custom logic needed to fulfill the user’s intent. The result type can be whatever you like.
- IsValid: Here you validate that the action is ready (ie. all mandatory members are resolved) and the fulfillment method can be called.
IMPORTANT: A new mandatory attribute is introduced in order to decorate your actions (classes) per the following:
This new attribute is mandatory because it defines which intent the action is for (Note:
intentName must match the intent name within your LUIS model), as the framework will search for types within your application which use this attribute (
LuisActionBindingAttribute) and implement the
ILuisAction interface when a call to resolve an action for an intent is made.
But what about the action members mentioned previously? In the beginning of this post, we defined action members as requirements for a specific action to be triggered, when bound to an intent. The action members will be situated at the class implementation which uses the
Adding Validation to your Actions (C#)
The framework provides an abstract class
BaseLuisAction which by default includes validations from the .NET Data Annotations as shown below:
To implement common validation logic for an action, the ILuisAction interface provides a default
FulfillAsync method you may implement at your class.
This allows for two flexible options for action validation:
1) From your action, inherit this class (
BaseLuisAction), and you are only required to implement the default
FullfillAsync method at your action class. This relies on the .NET Data Annotations for
built-in common validations, implemented at the
2) You may implement the
ILuisAction interface and include your own custom validation logic. Note that the
IsValid method is virtual, and can be extended to your actions as needed.
Implementing and Binding your Actions (C#)
In the following example, an intent of
GetWeatherInPlace is bound to an action of
GetWeatherInPlaceAction using a LuisActionBinding attribute at the class level.
In this Action, the
BaseLuisAction is also inherited.
NOTE: By convention, the name of an attribute class ends with the word
Attribute, however when applied to a class, the inclusion of the word Attribute is optional. See MSDN Custom Attributes.
From the example above the
Place property in the Action includes a
Required attribute. When this new action object is created, the
IsValid method inherited from
will check if the
Place contains a non-empty value. Assuming no validation errors,
IsValid will return
true, and then the action
GetWeatherInPlaceAction will be bound
to the intent of
Determining an Action based on the LUIS response (C#)
As mentioned, in the past Actions were bound to intents at the LUIS service. Under the old LUIS service, after a user submitted a request to LUIS, the service would determine an action, and return a reference to that action in the response. As Actions are no longer determined by LUIS, the client (developer) can determine the action to bind based on the returned Intent and recognized entities. In short, LUIS will now only determine intents and entities, and the client will determine the actions the intents and entities determined by LUIS.
To determine the action for a given intent, the framework provides a
LuisActionResolver class which receives a list of assemblies when instanced.
This resolver will go through the list of assemblies provided and search for all the actions defined in the application.
A summary of the steps in the example above:
1) An async request is made to the LUIS service and the return object is referenced in
2) Create a new instance of the
LuisActionResolver class, providing it with a list of assemblies from the application.
This class includes a method
ResolveActionFromLuisIntent which will be used to determine which action the intend will bind.
3) Invoke the
ResolveActionFromLuisIntent method, passing in the LUIS response (
luisResult), which returns the proper
action to bind, referenced in
So.. you might be wondering, why do I need to implement
ILuisAction if I need to provide the
luisResult (where the user’s intent is provided along with the recognized entities)? Up to now, there is no good reason to do all this as you need to call the LUIS service by yourself, create an action and map it to an intent via the binding attribute and finally implement the code for the fulfillment. You could perfectly have done all this with no need to implement
ILuisAction and use the binding attribute, nor even use the resolver. You could simply inspect
luisResult instance and execute the logic to resolve it.
Why all this ‘overhead’ then? Be patient, you will see how all this helps when you implement a Bot app that integrates all these concepts and resolves all the code shown above transparently (in particular the code to resolve missing entities until the action is valid and can be fulfilled).
Now we are ready to examine how we can incorporate Action Binding to our Bots.
Using LUIS Action Bindings in a Bot (C#)
Reference the Github Repo
Now that we are able to bind our Actions to Intents given the response from the LUIS service, the last step is to integrate the binded actions to the application Dialog.
In order to integrate all the features described before seamlessly the framework provides a
LuisActionDialog that you can inherit at your bot app. This dialog integrates
LuisIntentAttribute attributes decorating your intent handler methods, with the
ILuisAction implementations you might have (mapping those intents), getting the appropriate action fulfillment result for a particular user’s request and sending it back to the intent handler after being resolved.
For this, the
LuisActionDialog defines new valid signatures for the intent handlers that you can use at your custom dialog:
As said, the dialog receives the action binded to an intent by using the
LuisActionBindingAttribute.IntentName property within the
ILuisAction classes, and validates if the action is valid to be executed. If the action is not valid yet, the user will be prompted for non-valid member values until the
IsValid method from your action returns true. Finally, it calls the intent handler with the object you return at the
FulfillAsync method of your action.
At the client level for our Bot, we begin by importing the new module we’ve been discussing.
Reference: C# Samples Bot
So, how can a custom dialog be implemented which handles a user’s requests? Quite simply - your dialogs should only inherit from
LuisActionDialog as shown below, and
decorate your handler methods using the
As you might have seen the constructor for the
LuisActionDialog receives a list of assemblies where it will find
ILuisAction implementations decorated with the
LuisActionBindingAttribute, as well as several
ILuisService instances that will be used to resolve user’s messages.
The only requirement in order to use your actions with the
LuisActionDialog is that they should be serializable (include the
[Serializable] Attribute as shown above).
Source Code: NodeJS Action Binding Samples
From our app level we begin with requiring the new module which allows for Action Binding:
Action Binding in NodeJS
Determines which LUIS intent name the action should respond to.
The friendly name is used to be displayed in some cases, as for example when you are switching from one action to another a prompt might ask you to confirm the switch and the friendly names of the actions are being shown in the confirmation message
Defines the list of object properties the Action requires, their types and message to display to request the parameter. It is defined using schema-inspector. More information here.
Each parameter is defined using the following fields, plus any schema-inspector validation property:
Defines the expected type. See
schema-inspector typesfor valid options.
Defines the message displayed to the user when the parameter is missing or validation does not pass.
builtInType (string) - (Optional)
This is optional and is used to help identify the entity that should match to this parameter. See LuisActions.BuiltInTypes for valid options.
This function fulfills the user action. It receives the parameter values as a keyed-object and a callback function that should be invoked with the fulfillment result. Here you do whatever is required to fulfill the intent and provide a meaningful result.
Let’s examine an example of how we may incorporate these action members into an Action.
In the above Action binding example, only one parameteter
Place, is defined. You may include any number of parameters to your application as you need, by default they are required, however can be made optional by providing the parameter a key-value of
optional : true.
See the Github Repository for more NodeJS samples to implement LUIS Action Binding.
Using LUIS Action Binding - Action Models and Usage (NodeJS)
The framework provides an easy hook for integrating Action Binding within a Bot built using BotBuilder (as you’ll see in the next section), but a low-level construct is offered that can be used programatically with any application type. The Console and Web application samples makes use of this building block.
This option is provided by the LuisAction.evaluate method and has the following signature:
NOTE: You can check the TypeScript bindings as reference.
The input parameters are:
modelUrlis the LUIS.ai application url.
actionsis an array of Action Binding definitions.
actionModelreturned from a previous call. The first time you invoke this method, it should be null.
userInputis the current input string - typically submitted by the user.
onContextCreationHandleris an optional callback method to switch contexts when triggering contextual actions. Context handling is used to switch between different intents during conversation, based on the user’s input (ie. the user changed their mind and requests something else). Context switching is an in-depth topic worthy of it’s own dicussion and beyond the scope of this article, it is ok to disregard this for now.
The returned Promise resolves to an
actionModel from now on) that can be used to re-call the
evaluate function and, in that way, keep the context of the conversation and the action binding state.
This object contains the status if the action binding evaluation and, if fulfilled, the
Using LUIS Action Bindings within a Bot (NodeJS)
The key thing here is creating an
IntentDialog with a
LuisRecognizer, and bind the Actions array to the bot and dialog using the bindToBotDialog helper function. A summarized example is shown below:
The function also registers with the bot a new BotBuilder library that contains a single dialog, named
Then, it uses the internal
createBotAction function to route incomming messages with matching intents to the
When validation does not pass,
prompt messages are presented to the user to provide each of the missing or invalid parameters and continues until validation passes. Then the fulfill function is invoked, passing all validated parameters.
And that’s it! Everything in place to support Action Binding for your Bot in NodeJS.
We’ve showcased how to implement Action Binding on the client end for building applications using Microsoft’s LUIS service in both C# and NodeJS, both supported by the Microsoft Bot Framework. While LUIS Actions on the service side have been removed, this was to provide more control and flexibility to developer, who can now more strictly control how conversational flow works in their Bots!
In the Future, we will provide an overview of how to implement this concept for Web Applications (ASP.NET MVC), as well as Console apps.
Once again, you are welcome to the source code at the Github Repo.
Pablo Constantini, Ezequiel Jadib & Matthew Shim from the Bot Framework team