Botframework-sdk: [Question] In a multistep Waterfall dialog, responses to SuggestedActions and buttons are always received in the first step?

Created on 26 Oct 2017  路  5Comments  路  Source: microsoft/botframework-sdk

Bot Info

  • SDK Platform: Node.js
  • SDK Version: 3.11.0

Issue Description

In multistep Waterfall dialogs, user input can be requested at any step by means of Prompts, beginning new Dialogs, or rich messages.
The response to the request is received at the next waterfall step in the case of Prompts and child Dialogs, but the responses to rich messages are always received at the first step of the waterfall (out of context).

Code Example

bot.dialog('multiStepDialog', [
  (session, args)=>{
    // STEP 1
    // Results from STEP 3 SUGGESTED ACTIONS are currently received here!
    builder.Prompts.text(session, "Write anything");
  },
  (session, results)=>{
    // STEP 2
    // Result from STEP 1 PROMPT are received here
    console.log("Prompt response:", results.response);
    session.beginDialog('aChildDialog');
  },
  (session, results)=>{
    // STEP 3
    // Results from STEP 2 child Dialog are received here
    console.log("childDialog results:", results);

    // Let's create a message with some suggested text responses
    var msg = new builder.Message(session)
      .text("Do you accept the terms?")
      .suggestedActions(
        builder.SuggestedActions.create(
          session, [
            builder.CardAction.imBack(session, "YES", "Yes, of course!"),
            builder.CardAction.imBack(session, "NO", "No way!")
          ]
        ));
    session.send(msg);

    // Results from STEP 3 SUGGESTED ACTIONS should be received here?
  },
  (session, results)=>{
    // STEP 4
    // Results from STEP 3 SUGGESTED ACTIONS should be received here?
  }
]);

bot.dialog('childDialog', (session, args)=>{
  session.send("Hello from a child dialog");
  session.endDialogWithResult({response:{ok: true}});
});

Reproduction Steps

  1. Launch the example 'multiStepDialog' and interact with it
    Observe that:
  2. Step 1 sends a Prompt and its result is received by Step 2
  3. Step 2 launches a child Dialog and its result is received by Step 3
  4. Step 3 sends a message with Suggested Actions. Its result is received by Step 1.

Expected Behavior

The response to the message sent at Step 3 should be received in context, either at Step 3 itself (as it is the current step in the dialog stack and it has not requested to transition to the next step) or at Step 4 (to mimick the behaviour of a Prompt).

Actual Results

Currently, the response to the message sent by Step 3 is received by Step 1 (the first step of the Waterfall dialog), even if we were previously at Step 3 and we have not transitioned to Step 4 yet.

Step 1 should not know about later steps and the Suggested Actions and Buttons proposed by messages in later steps. Adding code to Step 1 to handle such user inputs does not seem appropriate and it is out of context.

Current Workarounds

In order to not receive the response at Step 1, I can see two workarounds, but they are no completely satisfactory:

  • Trigger an action: Create an action that is expecting the user input (e.g. the Suggested Action text or the Button data). This is not satisfactory because not every user input can be matched in a triggered action and even if you manage to catch it, it would be handled in the Action, not in the context of the multistep dialog.
  • Begin a child dialog: Instead of sending the rich message at Step 3, launch another (single step) dialog and delegate sending the message and processing the response to the child dialog. The results of this child dialog will be received in context in Step 4.
    This is the best current alternative, but it requires boilerplate code to create child dialogs, launch them and process its response.

Even if workarounds exist, I think that they should not be required, IMHO the response should be received in context by the current step in the waterfall.

By the way, the docs do not present any similar case so it is not clear what the proposed pattern is:

Most helpful comment

Thanks for the update.

The Bot Framework team is currently working on a complete re-write of the SDK in the v4 line to address problems like this.

Stay tuned for SDK4 preview coming early 2018.

All 5 comments

@SynerG What's happening here is you are confusing the behavior of prompts with the behavior of CardAction imback style messages sent by the suggestedActions in your message.

Prompts and imback messages are not the same thing, and are handled in totally different ways.

As you observed, the imback messages are not handled by the next step of the waterfall dialog. _This is the intended behavior._

To handle an imback message, you must add another dialog with a triggerAction specifically defined to recognize the imback message custom identifier.

Here I have updated your example code to add more debugging info, as well as added dialogs with triggerActions configured to handle the imback messages sent by your card's suggestedActions.

I hope this clarifies the intended behavior of the dialog stack as well as the differences between prompts and imback messages.

bot.dialog('multiStepDialog', [
    (session, args) => {
        session.send('[DEBUG] Inside Step 1')
        // STEP 1
        // Results from STEP 3 SUGGESTED ACTIONS are currently received here!
        session.send('[DEBUG] starting text prompt')
        botBuilder.Prompts.text(session, "Write anything");
    },
    (session, results) => {
        session.send('[DEBUG] Inside Step 2')
        // STEP 2
        // Result from STEP 1 PROMPT are received here
        session.say(`You said: ${results.response}`);
        session.send('[DEBUG] starting childDialog');
        session.beginDialog('childDialog');
    },
    (session, results) => {
        session.send('[DEBUG] Inside step 3');
        // STEP 3
        // Results from STEP 2 child Dialog are received here
        session.send(`childDialog results: ${results}`);

        // Let's create a message with some suggested text responses
        var msg = new botBuilder.Message(session)
            .text("Do you accept the terms?")
            .suggestedActions(
            botBuilder.SuggestedActions.create(
                session, [
                    botBuilder.CardAction.imBack(session, "imback-yes", "Yes, of course!"),
                    botBuilder.CardAction.imBack(session, "imback-no", "No way!")
                ]
            ));

        session.send('[DEBUG] sending message with suggestedActions buttons');
        session.send(msg);
    },
    (session, results) => {
        // STEP 4
        session.send('[DEBUG] Inside step 4')
        session.send('[DEBUG] The user will never get to this step because suggestedActions' + 
            'aka CardActions.imback messages are not processed by the dialog stack in the same way as prompts.');
    }
]);

bot.dialog('childDialog', (session, args) => {
    session.send('[DEBUG] inside dialog: childDialog');
    session.send("Hello from a child dialog");
    session.send('[DEBUG] calling session.endDialogWithResult, which will 1) End the current' + 
        'dialog "childDialog", and return the user to the parent dialog, aka "multiStepDialog".')
    session.endDialogWithResult({ response: { ok: true } });
});

bot.dialog('handleSuggestedActionsYesResponse', (session) => {
    session.send('You selected: "Yes, of course!" response');
    // calling endDialog() here will reset the dialog stack to the root '/' dialog
    // if you don't want to reset the dialog stack, aka start over, then call startDialog here instead
    session.endDialog();
}).triggerAction({matches: /imback-yes/i});

bot.dialog('handleSuggestedActionsNoResponse', (session) => {
    session.send('You selected: "No way!" response');
    session.endDialog();
}).triggerAction({matches: /imback-no/i});

Thanks for the feedback, however it does not solve the issue.
I know that Prompts and SuggestedActions currently behave very differently. And that you can write a new Dialog with triggerActions to try catch the reply.

What I tried to point out is that the current default behaviour, i.e. routing the message to the first step in thewaterfall, does not seem adequate and create undesired consequences.
It would be better to route the message to the current step in the waterfall.

This goes beyond SuggestedActions and imback, it is just one of the cases.

Let's take your code as an example.

With your modifications you expect that the reply to the question with suggested answers will be handled by the dialogs handleSuggestedActionsYesResponse or handleSuggestedActionsNoResponse.
However, if you send this conversation in Telegram (or any Channel that sends back the original text as the reply), the bot will receive the message "Yes, of course!" that is not handled by any of the dialogs, so the message will currently be routed to the first step of the waterfall!

Of course, the first step has no code to handle this reply and restarting the dialog may have undesired side effects.

Would not be more reasonable that, by default, the **current step** receives the message?

The current step may be prepared to handle alternative user inputs to the current question, but the first step has no clue about what is going on.

The Regexp of triggerAction() could be modified to also match "Yes of course!", but that is not a solution. If you support multiple languages, it would need a very complex RegExp to match also "S铆, por supuesto!" and any other language variant. You may not know all the variants in advance.

All this work only to ensure that the reply is routed to the step that is waiting for the reply.

And even then you may miss it, because the user could write anything. SuggestedActions are only suggestions, no mandatory. And the reply would end again in the first step of the waterfall that is not expecting it and has no clue on how to handle it.

Why are messages message being routed by default to the first step of the dialog?
It makes more sense to route them to the current step of the dialog

It sounds like your problem is specific to the Telegram channel, since it doesn't support imback messages.

Another way to handle a yes/no question response pair is to use a confirm prompt. This prompt is specifically designed to handle the yes/no "confirm terms" scenario.

Alternately, you can check if your bot is talking to a user on the Telegram channel, and send a different kind of message (not using suggestedActions or imback style messages).

Thank you for the feedback and options.

The issue is not specific of Suggested Actions as any kind of message is routed by default to the first step in the waterfall, not the current step. I used Suggested Actions with imback as an example. But sometimes I deliver my own messages to the bot using bot.receive() and those are also by default routed to the first step of the dialog, while the current one IMO would make more sense.

Regarding Telegram only, I have used Suggested Actions in Slack too and a similar issue happens. Slack renders Suggested Actions as text in the message that the user has to copy by hand in the reply. You cannot expect the user to write a computer-friendly message (e.g. type=100&color=w) so I use user-friendly localized strings (e.g. white shirt, camisa blanca, ...). So the same issue happens if you want to catch the reply: matchers should account for i18n and anyway the user could write something different from a suggested action.

The yes/no Prompt may be handy in this particular use case, I will take it into account. Although the issue has a broader approach.

The question sent to the user could have more than two suggested replies, but other replies could be valid too:

    When do you want to schedule your massage?
    - at 7pm
    - tomorrow at 8am
    - sunday 2pm

(These are known free time slots, but another slot could be available. If the user writes it, it would be checked before replying with an error.)

    What do you want to write on the gift card? Choose a suggested text or write your own
    - You are the best
    - I love you
    - Sorry!

(We may have specific decorations for these messages, but the user could write a different message for the card.)

Your proposal of using a different kind of message in the channels that do not accept Suggested Actions is also a good point.

Telegram kind of supports Suggested Actions (they call it ReplyKeyboard and shows up as buttons that the user can tap), but they send back the text of the button and not the associated data.

I typically choose between Postback buttons (Inline keyboards in Telegram) and Suggested Actions depending on the use case (rather than the channel). Postback buttons are easier to handle (and match) because they send back data, so you can support a limited set of replies that are independent of i18n, but the reply does not leave a explicit trace in the conversation.

For important steps, (e.g. the user requests a specific service), I prefer to use Suggested Actions because (at least in Telegram and Slack) the user actually sends a real message that is included in the conversation. So the user can more easily review the conversation. And the messages provide implicit prove that the user actually requested a specific service.

But beyond Suggested Actions, my remark was about the default route for messages that do not have a specific match (with a high score). The dialog stack has all the information about which is the active dialog and which is the current step in the dialog. This step is more likely the one that is expecting the message. But the routing algorithm is discarding this information, using only the dialog and routing the message to the first step of the waterfall by default.

There might be some good reason behind it, but I just wanted to point out that in some cases the current step is able to handle the message, but the first step has no clue about it and restarting the dialog could have side effects.

Thanks for the update.

The Bot Framework team is currently working on a complete re-write of the SDK in the v4 line to address problems like this.

Stay tuned for SDK4 preview coming early 2018.

Was this page helpful?
0 / 5 - 0 ratings