3

I am using API.AI to implement the assistant app, but now I found it's hard to looping on the same intent to collect different user input (correct me if my expression is wrong, will explain it in details.) The problem is, I have a list of elements, and each time I want to assign exact one element to one person (collected via use input Assistant.getArgument()), however, I want it speaks to the user every time like 'who do you want to assign element X to?' (X refers to the name of element in the list). My current implementation is, creating a separate function, make it ask the question and then do the collect input/assignment in the other function using a while loop, at the end of while body call the ask function, but it doesn't work as API.AI gives Not Available in the response. Any ideas on how to do this? Let me know if the there is something unclear.

Here is just a brief code snippet for showing what's the issue is & what I want to achieve. I want to make it asks 4 times in API.AI, get user input, and store them all in to the output string.

var output = '';

    function do_sth(assistant){
        let get_name_input = assistant.getArgument('name');
        output = output + get_name_input + '.';
    }

    function test_repeat(assistant){
        for(let i = 0; i < 4; i++){
            assistant.ask('What is the name?');
            do_sth(assistant);
        }
    }

2 Answers2

2

The problem is that programming for the Assistant is an event-driven system (each Intent is an event), and you end the processing of the event on the server with assistant.ask() or assistant.tell(). This sends your reply back to the user. ask() will then wait for another event, while tell() indicates that the conversation is over.

This means you can't put ask() inside a loop and you can't store results in a local variable, since each answer will come back to you as a new event (ie - a new call to your webhook each time).

Here is a way to do it. It consists of three parts:

  1. An intent (name.init in my screen shot) that is used to first call the webhook with an action name.entry and trigger the loop.
  2. An intent (name.loop in my screen shot) that responds when the name_loop context is active to get the name and send it to the webhook with the same action name.entry.
  3. A code fragment for handling the name.entry intent.

name.init

name.loop

Code

var loopAction = function( assistant ){
  const CONTEXT = 'name_loop';
  const PARAM = 'name';
  const VALUE = 'index';
  const NUM_NAMES = 4;

  // Get the context, which contains the loop counter index, so we know
  // which name we're getting and how many times we've been through the loop.
  var index;
  var context = assistant.getContext( CONTEXT );

  if( !context ){
    // If no context was set, then we are just starting the loop, so we
    // need to initialize it.
    index = 0;

  } else {
    // The context is set, so get the invex value from it
    index = context.parameters[VALUE];

    // Since we are going through the loop, it means we were prompted for
    // the name, so get the name.
    var name = assistant.getArgument( PARAM );

    // Save this all, somehow.
    // We may want to put it back in a context, or save it in a database,
    // or something else, but there are things to be aware of:
    // - We can't save it in a local variable - they will go out of scope
    //   after we send the next reply.
    // - We can't directly save it in a global variable - other users who
    //   call the Action will end up writing to the same place.
    loopSave( index, name );

    // Increment the counter to ask for the next name.
    index++;
  }


  if( index < NUM_NAMES ){
    // We don't have all the names yet, ask for the next one

    // Build the outgoing context and store the new index value
    var contextValues = {};
    contextValues[VALUE] = index;

    // Save the context as part of what we send back to API.AI
    assistant.setContext( CONTEXT, 5, contextValues );

    // Ask for the name
    assistant.ask( `Please give me name ${index}` );

  } else {
    // We have reached the end of the loop counter.

    // Clear the context, making sure we don't continue through the loop 
    // (Not really needed in this case, since we're about to end the
    // conversation, but useful in other cases, and a good practice.)
    assistant.setContext( CONTEXT, 0 );

    // End the conversation
    assistant.tell( `I got all ${index}, thanks!` );
  }
};
Prisoner
  • 49,922
  • 7
  • 53
  • 105
  • Hi Prisoner, I sort of understand your point and tried myself but it didn't work quite well. I guess I need to ask more on these: 1. Besides the steps you list, what should I do to set context in api.ai? Specifically, what's 'name_index' you're referring to, is it a input context or output context? 2.When you do setContext, how you choose to put for the parameter space? To me context is what you got from getContext, but shouldn't it be some intent parameters? I don't understand the meaning of passing this. If possible, as the sample code is pretty simple, can you post your solution to this? – Will Yuling Liu Aug 15 '17 at 17:09
  • I've updated my answer with screen shots and code, which should be more clear. – Prisoner Aug 17 '17 at 10:23
1

Without going into complications, let me offer you a simple solution if I understand correctly what you are trying to achieve.

The user is presented with the option of 3 pets , dog, cat and rabbit . And is asked to name them differently. You want to achieve it with one intent , let's say pet_name. Name of the action pet.name.

The solution is pretty simple. Create 3 parameters in those intent (and make all of them "required", by checking the box). 3 parameters are dog_name , cat_name, rabbit_name.

Now enable fulfillment for that intent, and get all the parameters in your web hook. Now you can use them directly in the out put text. Like : outputtext = $dog_name." is a great name for your puppy. Tell me more"; (you can activate it only when action=="pet.name" ).

  • dipayan thanks for the answer! It's kind different. The parameter is not set in this case, in other words the api.ai does not know what name the user will give as an input, so it's completely dynamic. Therefore, I want to make it back to test_repeat intent every time our end get the argument name through do_sth. – Will Yuling Liu Aug 16 '17 at 17:18
  • This is actually a great solution! But does require you to know exactly how many names you want to ask about up front. If you change the parameter names to something like "name_1", "name_2", "name_3" and so forth, it more closely matches what you're doing. – Prisoner Aug 17 '17 at 10:25