Moving from LUIS to CLU in Bot Composer Projects
Published Sep 28 2022 01:35 AM 7,108 Views

As LUIS Language Understanding enters end-of-life, I've been getting some requests for guidance on how to smoothly migrate to the new CLU (Conversational Language Understanding), the spiritual successor to LUIS and part of the new Azure Cognitive Service for Language product.


While this might seem daunting at first, it doesn't have to be a major headache if you know a few tips and tricks to make this migration smoother.


For this example, I'm going to start with a default template for Bot Composer using LUIS, which contains a set of pre-configured intents and utterances, and show you how to migrate from that to a solution using CLU.


Selecting a Bot Framework templateSelecting a Bot Framework template


After configuring Bot Composer with the LUIS authoring key and region, we can publish directly from the app and build and train a model based on the pre-configured intents, which in this case are 'Cancel' and 'Help', and the default 'None' intent.  


The LUIS portal ( allows us to export the model as a .json file. We will use this feature to export our model so we can then import it in the Azure Language Studio portal. If you don't know this one, it's where we will manage our Conversational Language Understanding model. It should feel at least somewhat familiar, as many of the concepts are the same.


Let's start by exporting the model. On the LUIS portal, open your conversation app and go to the "MANAGE" menu.


Navigating to the Manage menuNavigating to the Manage menu


Then, on the left side menu, open the "Versions" page, select the latest version of your model, click Export, and select "Export as JSON".


Exporting LUIS model to JSONExporting LUIS model to JSON


You should now have a .json file containing all your intents, the utterances used to train the model and some other metadata.


Now let's open Language Studio and import our model.

After you've signed in, the portal will ask you to create (or select) a new Language service in your Azure subscription. I had already created mine, so I just selected it.


Choosing a Language resource in Language StudioChoosing a Language resource in Language Studio


Now find Conversational Language Understanding under "Understand questions and conversational language" and click "Open Conversational language understanding".


Navigating to Conversational Language Understanding in Language StudioNavigating to Conversational Language Understanding in Language Studio


Once you open the page, you'll see the option to import the .json we previously exported to create a new project.


Importing LUIS model in Language StudioImporting LUIS model in Language Studio


Select your exported .json file and click import.


Dialog for importing LUIS modelDialog for importing LUIS model


You'll then be taken to a screen that looks very similar to the one in the LUIS portal, and if everything went according to plan you'll see your imported intents and utterances.


Schema definition for imported model in Language StudioSchema definition for imported model in Language Studio


Now we need to train the model to make it available for publishing.

On the left side menu, select "Training jobs" and then select "Start a training job".


Training the model in Language StudioTraining the model in Language Studio


We will need to fill out a name for our model and select the training mode. For this example my model will only be interpreting English, so I will choose standard training. I will also use the default split for the testing set. Once you've made your selections click "Train" in the bottom left of the page.


Setting up training in Language StudioSetting up training in Language Studio


Once you get notified that the training is complete, it's now time to publish the model. This exposes an HTTP endpoint which we will then use to send user utterances. Go to the menu on the left and select "Deploying a model". Then, click "Add deployment".


Deployment screen in Language StudioDeployment screen in Language Studio



Choose a name for your deployment and select the model we've trained previously and click "Deploy".


Adding a deployment in Language StudioAdding a deployment in Language Studio


Once it's done, select your model on the screen and click "Get prediction URL".


Getting deployment URL in Language StudioGetting deployment URL in Language Studio


Copy the prediction URL and the Sample Request. We'll use them in Bot Composer.


We're now done with the migration of the model from LUIS to CLU and have a working endpoint to send user utterances.


Now let's look at what changes we need to make in our Bot Composer project to accomodate these changes.

You can explore my own setup by cloning this repo.


Here you can see how my LUIS Bot Composer project looks like:


Sample Bot Composer project with CLUSample Bot Composer project with CLU



I'm using the Unknown Intent as my entry point for language recognition. Since CLU isn't officially supported as a feature in Bot Composer, you won't be able to take advantage of deep integration like you have with LUIS. Therefore, we are using the HTTP request action to POST to the endpoint we gathered from the Language Studio and parsing the response to capture the detected intent. We are then branching on the intent by using a switch action.


Let's look at it step by step, starting with the HTTP request.


HTTP request in sample projectHTTP request in sample project


As you can see, we are posting to the CLU endpoint, passing the Ocp-Apim-Subscription-Key as a header value. You can get this from the Sample Request you copied from the Language Studio.


Here's what the body looks like: 



  "kind": "Conversation",
  "analysisInput": {
    "conversationItem": {
      "id": "${}",
      "text": "${turn.activity.text}",
      "modality": "text",
      "language": "en-us",
      "participantId": "${participantId}"
  "parameters": {
    "projectName": "<Language studio project name>",
    "verbose": true,
    "deploymentName": "<Language studio deployment name>",
    "stringIndexType": "TextElement_V8"




Essentially, we are passing the activity id, the text captured from the user and the participant Id in the conversation item object. and the Language studio project name and deployment name in the parameters.


We are also saving the CLU result in a variable called 'turn.cluResponse'.


Next we are parsing the response from CLU and collecting the top intent to save it in a conversation variable.


Parsing top intent from CLU response to variableParsing top intent from CLU response to variable


And finally, we are branching the conversation based on the value of this variable and calling the new dialog using the "Begin new dialog" action.


Branching on detected top intentBranching on detected top intent


Final Remarks

My first idea was to use the Begin new dialog and pass it the variable as a parameter to avoid the switch statement and have a cleaner solution. There is a problem in that approach, however. There is an unresolved bug in the Bot Composer, related to how it handles the array of available dialogs in each context. There already is a bug filed but no estimation or even guarantee that it will be fixed, as development is now being concentrated on Power Virtual Agents. Although it is not elegant, the presented approach should be sufficient unless you have a huge list of dialogs to branch to from your conversation entry point.


This approach also works inside one of the subsequent dialogs, as you can simply use the "Prompt for question" action to continue collecting text to send to CLU.


Let me know what you think!

Version history
Last update:
‎Oct 10 2022 03:00 AM
Updated by: