4 Tips to Make Better Prompts

I have blogged about sentiment detection and relaxed prompts before. I have recently put the two together and came up with a good recipe for handling prompts. Let me show you why I needed it and how I dealt with it.

Curve Ball

The bot framework can throw you a curve ball if you’re not careful selecting your prompts choices:

I have vs. I have not

It is actually trying to be smart. The bot is not sure but believes with 61% confidence that the user said I have received it. And a clear opposite to the positive option - I have not received it - would match with even stronger 83% score. WAT.

For simple yes/no choices, the bot will try a regex:

1
2
EntityRecognizer.yesExp = /^(1|y|yes|yep|sure|ok|true)(\W|$)/i;
EntityRecognizer.noExp = /^(2|n|no|nope|not|false)(\W|$)/i;

It has a few tricks to help more complicated cases:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// [excerpt from EntityRecognizer.ts]
// value - one of the choices given to the Prompts
// utterance - the user's response
// both are trimmed and lower cased.

var tokens = utterance.split(' ');

if (value.indexOf(utterance) >= 0) {
score = utterance.length / value.length;

} else if (utterance.indexOf(value) >= 0) {
score = Math.min(0.5 + (value.length / utterance.length), 0.9);

} else {
var matched = '';
tokens.forEach((token) => {
if (value.indexOf(token) >= 0) {
matched += token;
}
});
score = matched.length / value.length;
}

61% is the result of computing 'Ireceivedit'.length/'I have received it'.length. The alternative I have not received it gives us an even better score as more tokens find a match.

Tip #1: Spend some time thinking about how you formulate your choices not to get trapped by the fuzzy match logic.

Ambiguity

Since I built my first chatbot last year, I often find myself consulting EPAM‘s clients on the technology and the approach, and go as far as helping their teams get off the ground building one.

My go-to technique to bootstrap the conversation is to ask a client to document their imaginary conversation with the bot as if it existed. I can get a lot from this simple exercise. I can recommend a proper delivery channel. It will help decide if they need a custom built NLU service or can get by with LUIS or API.ai. I will also use their dialogue to educate them about what’s easy with the commoditized AI and what’s not. Asking the bot to do three things at once, for example, may sound very natural, but will likely be a lot harder to handle.

I also try to disambiguate the prompts:

Bot >> Did you receive my email? [yes/no]
User >> no
Bot >> Did you check your spam folder?

I would instead have the bot say:

Bot >> Please check your spam folder. Did you find the email there? [yes/no]

This way the bot has no problem understanding what the positive yes and the negative no mean.

Tip #2. If the bot needs to ask a yes/no question, make it a yes/no question. Unless, of course, you want to spend time building smarter brains for your bot

Another example:

[IT Support, locked account scenario]

Bot >> You can either wait 15 minutes and try again
Bot >> or you can reset your password to unlock your account now
Bot >> What would you like to do?

You can give the user two mutually exclusive options and lock the prompt, but if you are like me and prefer to keep the prompts more open and relaxed, you might want to change the bot’s prompt to:

Bot >> You can either wait 15 minutes and try again
Bot >> or you can reset your password to unlock your account now
Bot >> Would you like to reset your password? [yes/no]

Last example:

[end of the dialog]

Bot >> Great! Anything else I can help you with?

It’s a very natural prompt, but I suggest you don’t let your bot ask it this way. Not unless you’re ready to handle an arbitrary reply. Instead, have the bot say something like:

Bot >> Great! I am glad I was able to help

Tip 3. Don’t solicit feedback from your user that you are not equipped to handle.

Sentiment

If you follow the first three tips, you are very likely to have more yes/no prompts in your dialogs than other binary questions. EntityRecognizer does a good job with a simple regex but you may want to dial it up a notch with sentiment detection.

The idea is simple. Prompt the user with a yes/no question but do it in a relaxed manner. Let the user answer with whatever they feel like if they don’t use the buttons. Then, let the Bot Framework try to understand if it was a yes or a no. If not successful, turn to sentiment detection and treat a positive expression as a yes and a negative as a no. And finally, if sentiment detection comes back inconclusive, re-prompt the user and this time lock the choices to yes/no.

Here’s a reusable macro:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// [module sentiment.js]
// github link to the full implementation is provided below

module.exports = {
detect: function (text, language = 'en', threshold = 0.05) {
},

confirm: function (question, reprompt) {
return [
// Step 1. Relaxed yes/no prompt via Prompts.confirm
(session, args, next) => {
builder.Prompts.confirm(session, question,
{
listStyle: builder.ListStyle.button,
maxRetries: 0 // <-- no re-prompt
})
},
// Step 2. Try Sentiment detection as an alternative
(session, args, next) => {
if (args && typeof(args.response) !== 'undefined') {
// The bot framework recognized a 'yes' or a 'no'
next(args);
} else {
// Turn to sentiment detection
this.detect(session.message.text)
.then(response => next(response))
.catch(error => {
console.error(error);
next();
});
}
},
// Step 3. Re-prompt if needed
(session, args, next) => {
if (args && typeof(args.response) !== 'undefined') {
// We have a yes/no
next(args);
} else {
// Inconclusive. Need to re-prompt.
reprompt = reprompt ||
'I am sorry, I did not understand what you meant. ' +
'See if you can use the buttons ' +
'or reply with a simple \'yes\' or \'no\'. ';

session.send(reprompt);

builder.Prompts.confirm(session, question,
{
listStyle: builder.ListStyle.button
// <-- maxRetries is not set, re-prompt indefinitely
})
}
}
]
}
};

And now we can easily use it in our dialogs thanks to the spread syntax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const sentiment = require('./sentiment');

bot.dialog('/addToCart', [
function (session, args, next) {
// ...
},

// <-- using the macro we've just created
...sentiment.confirm('Would you like to see a few recommendations?'),

// <-- next waterfall step will receive a proper yes/no
function (session, args, next) {
if (!args.response) {
session.endDialog('Alright');
} else {
showRecommendations(session);
}
}
]);

I am using this technique in my e-commerce chatbot example and here’s a link to the full sentiment.js

Tip #4: Make your prompts handling smarter with sentiment detection but be ready to lock the user into a yes/no decision if sentiment detection comes back inconclusive.


Sentiment detection is not without traps either:

1
2
3
4
5
6
7
> const sentiment = require('./app/sentiment');
undefined

> sentiment.detect('no, thanks!')
Promise { <pending> }

> SENTIMENT: 0.941164496065538 in no, thanks!

That was a very positive no, apparently :)

Cheers!