Skip to main content

Fallbacks and error handling

In dialog design, adding fallbacks to handle errors is extremely important since it's hard to predict the full variation of what users might say. With errors, we typically mean that we got some unknown input from users that we don't know how to handle (i.e. we didn't have an intent for it), or that we might not have gotten any input at all.

Catching errors#

Catching unknown input#

Narratory automatically catches unknown input and provides randomized, localized fallbacks for these, for example "Sorry, I didn't understand that" and "Sorry, what was that?" for English. Sometimes you want to override these, which you can do by using the built-in Intent ANYTHING as follows:

const likeBotsQuery: BotTurn = {  say: "Do you like bots?",  user: [    { intent: ["Yes"], bot: "I like you too!" },    { intent: ["No"], bot: "That makes me a bit sad" },    { intent: ANYTHING, bot: ["I see", "Interesting"] }  ]}

Catching silence / no input#

Currently, Narratory comes with a built-in, language-specific support for handling silence (or "no input") on Google Assistant. This will be exposed to creators shortly. For English, these inputs are (in escalating order):

"I didn't hear you""I still didn't hear you","Sorry. I can't hear you. Please try again later!"

Note: this is currently only supported on smart speakers since phones will wait for the user to press to talk if no response is picked up.

The first time we get a silence, the first one will be used. If we get another silence, the second one will be triggered. If we get a third no-input, the bot will end the conversation with the final note.

Handling errors#

Repairing turns#

Quite usually, you want to save or repair the turn if the user says something that you didn't understand. This means giving the user another chance. This is called a fallback in some dialog systems. In Narratory, this basically means that instead of moving on, as you would for a followup, you stay in the current turn when you repair. This is done as follows:

const likeBotsQuery: BotTurn = {    say: "Do you like bots?", user: [        { intent: ["Yes"], bot: "I like you too!" },        { intent: ["No"], bot: {            say: "That makes me a bit sad"        }},        { intent: ["Maybe"], bot: {            say: "Sorry, maybe is a bit to vague. Yes or no?",            repair: true        }},        { intent: ANYTHING, bot: {            say: "Sorry, I really need to know. Do you, or do you not?"            repair: true        }}    ]}

Note that in order to use repair, you have to to use the object type of the BotTurn syntax, i.e { say: "something to say" }.

If you don't set repair, repair is implicitly set to false, i.e. the turn will move on. If you set repair to true, it repairs the current turn. See the next section for how to repair the parent turn.

Confirming answers using Repair Parent#

In some cases you want to repair the parent turn instead of the current turn, for example when you create a confirmation of the user's input. For this, you can use a special version of repair, repair: "PARENT", to create an inline confirmation turn and repair the parent turn if the confirmation fails. An example, verifying a users inputted name, is shown below:

const nameQuery: BotTurn = {  say: "What is your name?",  answers: [    {      intent: nameIntent,      bot: {        say: "I heard _name, is that correct?",        answers: [          {            intent: yes,            followup: {              say: "Excellent. Let's move on"            }          },          {            intent: ["no"],            followup: {              say: "Sorry, could you say it again?",              repair: "PARENT"            }          },          {            intent: nameIntent,            followup: {              say: "_name, did I get it right this time?",              repair: true            }          }        ]      }    }  ]}

Note the two different uses of repair above, the first one repairing the initial turn "What is your name" whereas the last one repairs the current turn "I heard _name, is that correct?". The distinction is important since the NLU in Narratory always is context-dependent.

Reprompting with escalating detail#

In dialog design, best practise is often to use the concept escalating detail and rapid reprompting when you misshear a user. This means that for every sequential fallback, we are more elaborate to help the user out. In other words, the first time you say "Sorry, I didn't hear you" you typically don't have to repeat the question whereas this might be needed after two fallbacks, and likely you want to formulate the question in a different way.

This is accomplished by combining the above concept repair and conditionals (see logic and conditionals) using the system set variable retryCount. An example of this follows:

const likeBotsQuery: BotTurn = {  say: "Do you like bots?",  user: [    { intent: ["Yes"], bot: "I like you too!" },    { intent: ["No"], bot: "That is sad" },    {      intent: ANYTHING,      bot: [        {          cond: { retryCount: 0 },          say: ["Sorry?", "Excuse me?"],          repair: true        },        {          cond: { retryCount: 1 },          say: "Sorry, I didn't get it. Was it yes or no?",          repair: true        },        {          say:            "Sorry, I seem to have issues understanding you. Do you like bots like me?",          repair: true        }      ]    }  ]}

In the above, a worst-case scenario would be:

Bot: Do you like bots?User: Bla bla blaBot: Sorry?User: Bla bla blaBot: Sorry, I still didn't get it. Was it yes or no?User: Bla bla blaBot: Sorry, I seem to have issues understanding you. Do you like bots like me?

Moving on#

Now, the above is pretty bad and at this point it might be best to either move on in the dialog or even stop it, especially if this question is crucial to the interaction.

An example of moving on in the dialog after two attempts (the difference here being that we don't set the repair flag for the last followup turn):

const likeBotsQuery: BotTurn = {  say: "Do you like bots?",  user: [    { intent: ["Yes"], bot: "I like you too!" },    { intent: ["No"], bot: "That is sad" },    {      intent: ANYTHING,      bot: [        {          cond: { retryCount: 0 },          say: ["Sorry?", "Excuse me?"],          repair: true        },        {          cond: { retryCount: 1 },          say: "Sorry, I didn't get it. Was it yes or no?",          repair: true        },        { say: "I'm sure you will like me. Let's proceed" }      ]    }  ]}
const nextTurn = "This is a new turn"

In the above example, the bot would move on after two failed attempts, resulting in this dialog:

Bot: Do you like bots?User: Bla bla blaBot: Excuse me?User: Bla bla blaBot: Sorry, I still didn't get it. Was it yes or no?User: Bla bla blaBot: I'm sure you will like me. Let's proceed.Bot: This is a new turn

It can be quite effective to use this type of avoidant/deflective fallbacks if you are doing a chit-chat dialog where you don't ask critical questions that you necessarily need to have answers for.

Exiting#

Another example that instead exits (see Exiting conversations) since the question was deemed necessary by the dialog designer:

import { EXIT } from "narratory"
const likeBotsQuery: BotTurn = {  say: "Do you wanna pay with card or bitcoin?",  user: [    { intent: cardIntent, bot: "Very nice" },    { intent: bitcoinIntent, bot: "Okay, crypto it is" },    {      intent: ANYTHING,      bot: [        {          cond: { retryCount: 0 },          say: ["Sorry?", "Excuse me?"],          repair: true        },        {          cond: { retryCount: 1 },          say: "Sorry, I didn't get it. Was it card or bitcoin?",          repair: true        },        {          say:            "Sorry, it seems we have problems communicating. Let's try again later!",          goto: EXIT        }      ]    }  ]}

Overriding default fallbacks#

If you want to override the default fallbacks, you can do so by supplying an array of phrases to the agent's optional defaultFallback parameter as follows. We recommend you to have at least 5 different phrases since you want to avoid repeating yourself (see Design recommendations).

const agent: Agent = {  // ... other parameters
  defaultFallbacks: [    "I didn't catch that, sorry",    "I missed that, could you repeat please?",    "Sorry, could you say that again?",    "Sorry, can you say that again?",    "Can you say that again?",    "Sorry, I didn't get that. Can you rephrase?",    "Sorry, what was that?",    "One more time?",    "What was that?",    "Say that one more time?",    "I didn't get that. Can you repeat?",    "I missed that, say that again?"  ]}

Query repeat - bots repeating themselves#

In spoken interactions it might happen that a user asks the bot to repeat itself. Narratory comes with default, localized handlers for this and will by default repeat what it just said - or if possible use an alternate formulation - if users ask something along the lines of "what did you say" or "can you say that again".

You can disable this default behavior by setting skipQueryRepeat to true on your Agent.