How to create a troubleshooting conversation

Hello.

When the user says something like “my internet is bad”, I want the bot to start troubleshooting using a series of questions.

Obviously, when the user tells the bot that it worked, the process should stop. If not, it will continue until the final question where the bot says “a case was created”.

For the sake of simplicity, let’s say the chatbot asks “does it work now?” after every step, except some steps where I care about the given information (just to store them and send them later, they don’t affect the conversation) like “do you have an Internet line?”.

I was wondering about the best way to implement that. Forms? With one slot for each answer? And how do I stop the form once the user answers “yes it works” to a question except for some?

If all the questions needed to be answered with “yes” to stop the form, that would have been simple:

- story: User fills troubleshoot form and stops
  steps:
  - intent: troubleshoot
  - action: form_troubleshoot
  - active_loop: form_troubleshoot
  - intent: affirm
  - action: action_deactivate_loop
  - active_loop: null

I could use Dynamic Form Behavior to “unlock” the next troubleshooting question, yet I don’t have a clear idea how yet. Do I just nest ifs and append the next slot each time?

And in that case, how do I make the bot utter “a case was created” only if the last question “do you have an Internet line?” was answered?

Finally, is there a way to call events inside the required_slots() and validate_<slot_name>() methods?

I ended up nesting ifs in the FormValidationAction’s required_slots() method.

For most slots, the question (utter_ask_<slot_name>) ends with “Does it work now?”, so they can be answered with “Yes” or “No”.

If anyone ever needs it, that’s the logic behind it:

In the domain, they are defined like so:

slots:
  noise:
    type: bool
    initial_value: null
    auto_fill: false
    influence_conversation: true
...
forms:
  form_troubleshoot_internet:
    noise:
    - intent: affirm
      not_intent: stop
      type: from_intent
      value: true
    - intent: deny
      not_intent: stop
      type: from_intent
      value: false
...

And I use nested ifs in the action, appending the next required slot each time the problem was not fixed:

    async def required_slots(self, predefined_slots, dispatcher, tracker, domain):
        text_if_works = 'Great! I\'m glad that it works now. Anything else I can help with?'

        required_slots = ['noise']
        if tracker.get_slot('noise') == True: # There is noise on the line, stop
            dispatcher.utter_message('Please contact xxxx to resolve the noise on the line. Anything else I can help with?')
        else: # There is no noise on the line, continue
            required_slots.append('modem_on')
            if tracker.get_slot('modem_on') == True: # The modem is on and it works, stop
                dispatcher.utter_message(text_if_works)
            else: # The modem is on and it doesn't work, continue
                required_slots.append('modem_green')
                if tracker.get_slot('modem_green') == True: # The LED is green and it works, stop
                    dispatcher.utter_message(text_if_works)
                else: # The LED is green and it doesn't work, continue
                    # Here, nb_phones and nb_sockets are extra information, the form will continue no matter what the answer is, so I extend required_slots by these two and the next decisive slot
                    required_slots.extend(['nb_phones', 'nb_sockets', 'splitter_installed'])
                    if tracker.get_slot('splitter_installed') == True: # The splitter is properly installed on all phones and modems and it works, stop
                        dispatcher.utter_message(text_if_works)
                    else: # The splitter is properly installed on all phones and modems and it doesn't work, continue
                        required_slots.append('rj_plugged')
                        if tracker.get_slot('rj_plugged') == True: # The RJ11 is plugged in and it works, stop
                            dispatcher.utter_message(text_if_works)
                        else: # The RJ11 is plugged in and it doesn't work, continue
                            required_slots.append('other_plug')
                            if tracker.get_slot('other_plug') == True: # The modem was plugged somewhere else and it works, stop
                                dispatcher.utter_message(text_if_works)
                            else: # The modem was plugged somewhere else and it doesn't work, continue
                                required_slots.append('other_modem')
                                if tracker.get_slot('other_modem') == True: # Another modem is plugged in and it works, stop
                                    dispatcher.utter_message(text_if_works)
                                else: # Another modem is plugged in and it doesn't work, continue
                                    # Like before, these slots are extra information, the form will continue and end no matter what the answer is
                                    required_slots.extend(['has_pbx', 'has_line', 'username'])
        
        return required_slots

Then after the form ends, in the Custom Action that happens after the form ended (no matter where it ended), I can do whatever I want with those slots according to which ones were filled.

For example, if the username slot was set (which means all slots were filled), it will send the slot values to a database.


And nope. We can’t do that.