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...
The bot responds as expected. But look what happens when the user enters the same text as a single input.
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.
Great!
Updated over 6 years ago