actions-on-google / actions-on-google-nodejs

Node.js client library for Actions on Google
https://actions-on-google.github.io/actions-on-google-nodejs
Apache License 2.0
900 stars 193 forks source link

V2 Alpha Discussion: Multiple Intent Name Matching #108

Open Canain opened 6 years ago

Canain commented 6 years ago

Hi all Actions on Google Node.js developers!

Now that the alpha of the v2 Client Library has been out for a few weeks, we would like to get feedback on the behavior and best practices of intent name matching, which we changed from action name matching to simplify the handling for most developers.

Sometimes, you may want to have a single intent handler for multiple intents. This can be understood as an OR match for the intent name.

Solution 1

The easiest way to do OR matching in JavaScript would be to have two app.intent calls registering the same function on two different intent names.

const handler = conv => {
  conv.ask('How are you?')
}

app.intent('intent1', handler)
app.intent('intent2', handler)

While declaring and reusing functions for OR Matching is not hard to understand, it makes the intent handler function disconnected from the intent name matching and makes it harder to see the entire intent name -> handler flow.

Solution 2

Currently, we are implementing the option to use a string instead of a function for the app.intent handler argument, which means to redirect the handling to the handler registered for the specified string intent name.

app.intent('intent1', 'intent2')
app.intent('intent2', conv => {
  conv.ask('How are you?')
})

While this solution makes the intent handler clearer and more connected, the structure of the code is not completely intuitive.

Solution 3

A possible way to make Solution 2 clearer is to explicitly specify the redirect, like in the app level call:

app.redirect.intent('intent1', 'intent2')
app.intent('intent2', conv => {
  conv.ask('How are you?')
})

Or a conv level call:

app.intent('intent1', conv => conv.redirect.intent('intent2'))
app.intent('intent2', conv => {
  conv.ask('How are you?')
})

Solution 4

Another possible solution is to use regex for intent matching, which opens up even more advanced use cases. However, for something simple like OR matching, it seems too complicated or intimidating for a lot of developers.

app.intent(/intent1|intent2/, conv => {
  conv.ask('How are you?')
})

Using a string array for the intent name matching is another possible solution, but it’s still not ideal as it‘s not super clear. Also, if we decide to use a similar system to do advanced boolean matching for other stuff like Dialogflow contexts, an array could mean either OR or AND.

app.intent(['intent1', 'intent2'], conv => {
  conv.ask('How are you?')
})

A Request for Developers

As you can see, this problem is very challenging to solve and implement a solution, so we would like to get feedback from you.

Let us know what you think so we can change the behavior during the alpha. We can even change the behavior after the alpha as long as the new solutions are not backwards breaking. The important thing for us to know is what to use for the samples to encourage best practices.

dustincoates commented 6 years ago

+1 for solution 3. I think it makes it very clear what the purpose of the code is, and what's happening.

Solution 4 looks great (especially the regex matching!) but I'm having a hard time trying to picture when I would use this that isn't too difficult to implement with one of the other solutions.

grctest commented 6 years ago

RE Solution 3:

Could the redirect be used like the following?

app.intent('intent1', conv => {
  if (condition) {
    conv.ask('How are you?')
  } else {
    conv.redirect.intent('intent2')
  }
})

Like so you could redirect the user to a fallback intent directly, instead of a fallback function?

joshbenner851 commented 6 years ago

Definitely the first part of Solution #3, however I'd consider renaming "redirect" to something more intuitive. What you're doing is sharing fulfillment or the action to be performed between two intents, so it's not necessarily a redirect for both intents. A redirect to me would more so be when you're redirecting a user from a failed conversation to a fallback intent

yoichiro commented 6 years ago

In the previous version, there was the routing mechanism using an action name. However, in current version 2, we must use the routing mechanism using an intent name. I would like to come back the action name routing mechanism, and I think that we should provide it for many developers. The reasons are follows:

1st Reason: Intent Name as Identifier

Until the previous version, we could use the intent name as explanation. Especially in the multibyte language environment, most intent names might have some multibyte characters and some whitespaces. That is, the intent name was not an identifier, and all intent names were not refered from anywhere. As the result, we could change all intent names at anytime. However, in the latest version, we cannot change all intent name easily because there is a possibility to be referred from a fulfillment code.

Also, generally, we don't use some multibyte characters and whitespaces for an identifier. In the latest version, the intent name is an identifier. That is, we must think that we cannot use multibyte characters and whitespaces for intent names. This means that a purpose of the intent name changes largely from explanation to identifier.

I think that all intent names should be clear and useful. That is, we should use multibyte characters and whitespaces for the purpose.

In addition, if using multibyte characters, we might face a possibility regarding a charactor code problem.

2nd Reason: Fulfillment becomes complex

If there is an action in the architecture of an intent and a fulfillment, we can use one action from multiple intents. This brings us flexibility. For example, if we define two intents and they refer one action from both, we can get each analysis of the intents separately in Dialogflow. At the same time, we also can keep the fulfillment logic simplify, because we only need to imlement one action logic.

However, in the latest version, I think that we lost the benefit. We always must define handlers for all intents in the fulfillment. I think that this is redundant. We had both felxibility and simplify brought by the action. The intent mapping looks simple, but I think there is no flexibility and the fulfillment logic becomes complex.

My Proposal: Introduce an action routing mechanism

In the latest version, we can use the intent-name-based routing mechanism as like the following:

const app = dialogflow();
app.intent('Default Welcome Intent', conv => {
  ...
});

In the future version, if an intent name is not matched for all intent handlers, my proposal is that we can handle an action name as like the following:

const app = dialogflow();
app.action('input.welcome', conv => {
  ...
});

Probably, I think that they can be reconciled. How about this?

Vissu-A commented 5 years ago

RE Solution 3:

Could the redirect be used like the following?

app.intent('intent1', conv => {
  if (condition) {
    conv.ask('How are you?')
  } else {
    conv.redirect.intent('intent2')
  }
})

Like so you could redirect the user to a fallback intent directly, instead of a fallback function?

this solution in more easy to invoke one intent from another intent