0

I'm trying to cancel o reset a flow meanwhile response collect question, this is necessary if a customer choose a bad option and he wants to cancel, but now I just quit of collect adding a Validation to Collect and when complete max_attempts, I redirect to main.

I wanna write CANCEL and redirect to Main, I tried whit a function but validations on collect don't let me send the redirect from function to Main Task.

Any idea?

Thanks a lot.

This is my function:

exports.handler = function(context, event, callback) {
var got = require('got');

var value = event.CurrentInput;

if(true){
    let responseObject = {
        "actions": [
            {
                "redirect": "task://greetings"
            }]
    };
    callback(null, responseObject); 
}};

Later I called it in a webhook, with onCollectAttempt but it doesn't happen anything, just continue the flow Collect

Function Webhook

  • Hi there! What does your function code look like? Are you using Twilio Functions (https://www.twilio.com/console/functions/manage)? Just to confirm--you have a task the user is in, and then you want them to redirect back to the start of the flow or a specific task? – lizziepika Feb 28 '20 at 19:20
  • Hi @lizziepika, I just edit the question I add the function, but as I told the flow of collect continue, but basically I want to write **CANCELAR** and redirect to the greetings task. – adventure_galley Feb 28 '20 at 20:48
  • Just to understand, you want to write `CANCELAR` while in the middle of a Collect flow? – lizziepika Feb 28 '20 at 22:02
  • Yes, that's exactly I want to do – adventure_galley Feb 28 '20 at 22:14

1 Answers1

1

Twilio developer evangelist here.

There isn't an easy way to do this since the current JSON bin of the Collect Function does not allow “canceling” or "resetting", but here is a potential work-around to use with a dynamic Collect (you use a Twilio Function to write it in Node.js instead of writing it in the JSON bin of the Autopilot console.

Below we have: 1. The Collect set of questions we need (to cover many collects from a single function we would need something additional like pulling from db/passing them in dynamically) 2. Agent keywords for the use case where we are ok to use static word check, e.g. instructing user to type "agent" at any time to be directed to human.

//code courtesy of Evangelos Resvanis, Senior Solutions Engineer at Twilio 
const questions = [
    {
        "question": "You want to book appointment?",
        "name": "book"
    },
    {
        "question": "When do you need the appointment for?",
        "name": "appointment_date"
    },
    {
        "question": "And what time?",
        "name": "appointment_time"
    }
];
const agent_keywords = ["agent", "i want to speak to agent", "let me talk to someone"];
var stringSimilarity = require('string-similarity');
const COLLECT_TASK = "collect_task";
/*************************** Main Function ********************/
/*
This function generates the collect questions in steps. Each step we enter the function again and check the user input. We keep a counter to exit when 
the collect questions are exhausted.
Two main use cases examined (any use case can be built)
- We want to break the collect on specific user input/keywords e.g. "agent". We use string comparison (Use Case 1)
- We want to send the user input to the Queries API to see if they provided fields. We use the API to avoid the Collect validate, as we can't break the Collect
from there (Use Case 2)
Uncomment/comment each use case to examine the results.
*/
exports.handler = function(context, event, callback) {
    let count;
    //If event.count is set, then we are at least in the first answer from the collect list
    if(event.count) {
        count = parseInt(event.count);
        //Use case 1
        /*var matches = stringSimilarity.findBestMatch(event.CurrentInput, agent_keywords);
        if (matches.bestMatch.rating > 0.5){
            callback(null, {"actions": [{"say": "Sure, handing over to one of our agents now..."}]});
        }
        else buildCollect(count);*/
        //Use case 2
        getFieldsOrNextTask(event.CurrentInput, count);
    }
    else {count = 1;buildCollect(count, null);}
    /*
    Send the user input to Queries endpoint
    - If there are fields and confidence is high, assume user input is ok, remember the value and continue to next question (improvement: check the exact type of field found)
    - If there are no fields, check the results of the task and confidence, assuming the user wants to break the collect. Choose the target task based on rules you test, excluding the collect task
    */
    async function getFieldsOrNextTask(input, count){
        const client = require('twilio')(context.ACCOUNT_SID, context.AUTH_TOKEN);
        let fields_result = await client.autopilot.assistants(context.AUTOPILOT_SID).queries.create({language: 'en-US', query: input, tasks: COLLECT_TASK});
        if(fields_result.results.task_confidence < 0.4 || !fields_result.results.fields.length){
            let all_tasks_string = "";
            let tasks = await client.autopilot.assistants(context.AUTOPILOT_SID).tasks.list();
            for(let i = 0; i < tasks.length; i++){
                if (tasks[i].uniqueName === COLLECT_TASK)continue;
                let name = tasks[i].uniqueName;
                let unique_name = i === tasks.length-1 ? name : name + ",";
                all_tasks_string += unique_name;
            }
            console.log("All tasks without the Collect task: " + all_tasks_string);
            let task_result = await client.autopilot.assistants(context.AUTOPILOT_SID).queries.create({language: 'en-US', query: event.CurrentInput, tasks: all_tasks_string});
            let task_confidence = task_result.results.task_confidence;
            let chosen_task = parseFloat(task_confidence) < 0.4 && !task_result.results.next_best_task ? "fallback": task_result.results.task;
            console.log("Chosen new task to break Collect is: " + chosen_task);
            callback(null, {"actions": [{"redirect": "task://" + chosen_task}]});        
        }
        else{
            console.log("seemed a valid field of the Collect task, remember it and move to next question");
            buildCollect(count, event.CurrentInput);
        }  
    }
    function buildCollect(count, userInputToRemember){
        console.log("Current count: " + count);
        if(count > questions.length){
            callback(null, {"actions": [{"say": "Thank you, that is the end of our questions, moving to the next part of the flow"}]});
        }
        else{
            //Create the current collect object and increase the count in on_complete for the next iteration
            let name = "collect_" + count;
            let on_complete = {
                    "redirect": "https://" + context.DOMAIN_NAME + "/dynamic-collect?count=" + (count+1)
            };
            let collect_object = {};
            collect_object.name = name;
            collect_object.questions = [];
            collect_object.questions[0] = questions[count-1];
            collect_object.on_complete = on_complete;
            console.log("Current Collect object: " + JSON.stringify(collect_object));
            if(userInputToRemember){
                let remember_var = questions[count-1].name;
                console.log(remember_var);
                callback(null, {"actions": [{"remember": {remember_var: userInputToRemember}},{"collect": collect_object}]});
            }
            else{
                callback(null, {"actions": [{"collect": collect_object}]});
            }
        }
    }
};

Let me know if this helps at all! <3

lizziepika
  • 3,231
  • 1
  • 14
  • 23