Handle Multiple Intents In One Input

What to do when your user's express more than one intent in a single input.

Ideally, your user would express one intent per input, but that's not always how people naturally communicate. What if the user enters several sentences of text, each of which expresses a different intent? By default, your NLU agent probably won't respond well. Take a look at this example...

353

The bot responds as expected. But look what happens when the user enters the same text as a single input.

352

The same text, submitted in one input, breaks the bot. We can make our bot more robust using a custom Python component. Take a look at the code below, then we'll walk through it.

# -*- coding: utf-8 -*-
from meya import Component
import re
import requests

class NLUSentences(Component):

    def start(self):
        
        API_BASE_URL = "https://api.dialogflow.com/v1/query"
        PARAMS = {
            "v": "20150910",
            "sessionId": "YOUR_SESSION_ID",
            "lang": "en",
            "query": ""
        }
        HEADERS = {"Authorization": "Bearer YOUR_CLIENT_API_TOKEN"}
        
        text = self.db.flow.get("value")
        prog = re.compile("(.+?[!.?]+)")
        result = prog.findall(text)
        intent = "no_match"
        messages = []

        for group in result:
            PARAMS["query"] = group
            data = requests.get(API_BASE_URL, params=PARAMS, headers=HEADERS).json()

            try:
                # This field will only exist if an intent matched.
                intent = data["result"]["metadata"]["intentName"]

                # Stop when we find a matching intent.
                break
            except:
                pass

            try:
                intent = data["result"]["action"]
                if intent.find("smalltalk") == -1:
                    continue
                message = self.create_message(text=data["result"]["fulfillment"]["speech"])
                messages.append(message)
            except:
                continue
            
        return self.respond(messages=messages, action=intent)

Here's what the code above does:

Lines 10-17 are required to make the API calls to Dialogflow. sessionId is a string you make up, up to 36 characters in length.

text = self.db.flow.get("value")
First we grab the user's input.

prog = re.compile("(.+?[!.?]+)")
result = prog.findall(text)
We're using regex to break apart the input into sentences.

intent = "no_match"
messages = []
By default we'll assume that no match will be found.

for group in result:
The regex code from above stored any sentences it found in result. Now we'll go through each sentence and submit it to Dialogflow via an API call.

PARAMS["query"] = group
Make the search query equal to the current sentence.

data = requests.get(API_BASE_URL, params=PARAMS, headers=HEADERS).json()
Make the API call and store the JSON response in data.

intent = data["result"]["metadata"]["intentName"]
We grab the name of the matched intent, if it exists.

break
Exit the loop once we find a matching intent.

intent = data["result"]["action"]
If the sentence matched something in Dialogflow's Small Talk content, the intent will be stored in ["action"] instead of ["metadata"]["intentName"]. We'll treat small talk a little differently than other intents. Instead of using small talk to trigger a flow, we'll just get the NLU agent's response and display it to the user.

if intent.find("smalltalk") == -1:
continue
There are many small talk intents available in Dialogflow, but we don't care which one was matched.

message = self.create_message(text=data["result"]["fulfillment"]["speech"])
Get the NLU agent's response and create a message from it.

messages.append(message)
If you didn't already know, your components can return a list of messages, not just one. The list can contain a combination of card messages and text messages. We'll append the small talk response to our list of messages to be displayed to the user.

continue
If we didn't find an intent or small talk, move on to the next sentence.

return self.respond(messages=messages, action=intent)
Finally, we return the list of messages, if any, and the intent as an action. Recall that if we didn't find an intent, intent will equal no_match. Also, notice that we're using messages= instead of message= since we're sending back a list of messages.

Now, let's setup a flow that will capture the input and send it to our component. Take a look at the code below before moving on.

states:
    # First, try to break the text into sentences and look for possible matches.
    start:
        component: nlu_sentences
        transitions:
            no_match: respond
            account_downgrade: account_downgrade
            account_upgrade: account_upgrade
            agent: transfer_to_agent
            pricing: pricing

    respond:
        component: meya.text
        properties:
            text: "{{ cms.general.catchall }}"
        return: true

    account_downgrade:
        component: meya.start_flow
        properties:
            flow: account_downgrade
        return: true

    account_upgrade:
        component: meya.start_flow
        properties:
            flow: account_upgrade
        return: true

    transfer_to_agent:
        component: meya.start_flow
        properties:
            flow: transfer_to_agent
        return: true

    pricing:
        component: meya.start_flow
        properties:
            flow: pricing
        return: true

Make sure the flow's Trigger Type is catchall. What we're building is essentially a routing flow. If we aren't able to route the user's input to an trigger, the no_match action will transition to the respond state.

The first state uses our new nlu_sentences component. You'll need to create a transition for every possible intent Dialogflow could return. In this example, my Dialogflow agent only has four intents: account_downgrade, account_upgrade, transfer_to_agent, and pricing, so I've created a transition for each one, as well as a state that will launch the appropriate flow.

Let's try it out.

352

Great!