Feature Proposal: Input Validation


#1

Hey all,
we are always trying to improve the way you can build your voice applications with the Jovo Framework, and although speech recognition and natural language understanding (nlu) work very well in many cases, false matches for user input can still happen.

This is why we want to introduce a new feature proposal called Input Validation and want to hear your opinions on this topic, what is to be improved on the current design implementation and what you would like to see added to this!

Problem
We encountered some problems with false input matching internally, where validation as a feature in some form would’ve been very helpful. For example, we built an Alexa Skill that was dependent on a custom slot which was only allowed to hold four values, otherwise we wanted the skill to reprompt the user.

In another case we needed the Alexa-built-in slot “AMAZON.DE_CITY” to recognize different German cities, but noticed that sometimes it understood “Wedding” (a district of Berlin) instead of Berlin itself. We noticed this for other cities as well, on both Alexa and Google Assistant/Dialogflow.

Current Solution
Due to the lack of this feature so far, we needed to solve these problems manually.

Take the first issue for example. If we generalized this to an intent, that only accepts four car brands, we would need to check the inputs for these values manually like so:

// app.js
    
CarBrandIntent() {
    if(['Ford', 'Mercedes', 'Ferrari', 'Mini'].indexOf(this.$inputs.car.value) === -1) {
        // If input contains car brands apart from the specified ones, go to Unhandled intent
        return this.toIntent('Unhandled'); 
    }
        // continue with intent
}

For the second example, a simplified manual solution would look like this:

// app.js
    
CityIntent() {
    let city = this.$inputs.city.value;
    switch(city) {
        case 'Wedding': city = 'Berlin';
    	    break;
    	case 'Maunheim': city = 'Mannheim';
    	    break;
    }
    // continue with intent
}

Implementation Proposal
To solve these and more input matching issues, we want to introduce a new field validation to the app configuration in config.js.

// config.js
    
validation: {
    ExampleIntent: {
        input1: new ValidValuesValidator(['valid1', 'valid2'], 'Unhandled'),
    	input2: [
    	    new IsRequiredValidator(),
    	    {
    	        validate() {
    		        if(this.$inputs.input2.value === 'test') {
                        this.toIntent('Unhandled');
    		        }
    		    }
    	    }
        ],
    	input3: function() {
    	    // validate
    	}
    }
}

validation will contain pre-defined validators or your own validation functions for specified intents and inputs. Since the configuration file is written in Javascript, you can also choose to import these from other files.

The idea is to assign a number of validators to the input that is to be validated. This is possible by assigning a single validator to your input name, as well as an array or a function.

// validation
    
ExampleIntent: {
    input1: [
        // Pre-defined validators
    	new ValidValuesValidator(),    // define values the current input is allowed to adopt
    	new InvalidValuesValidator(),  // define values the current input is not allowed to adopt
    	new IsRequiredValidator(),     // fails if current input is not present in this.$inputs
    	new ReplaceValuesValidator()   // replace all values for current input with a specified one 
    ]
}

Each validator accepts a number of parameters, depending on its functionality. For example, the ValidValuesValidator accepts an array of strings, that it will match the input against. Another parameter will be the handler. If the validation fails, for this instance the input has a value that is not valid against the specified values, the validator will redirect the user to this handler. It can consist of an intent name and an optional state.

// validation
    
ExampleIntent: {
    input1: [
    	new ValidValuesValidator(
    		[
    			'validValue1',
    			'validValue2'
    		],
    		'Unhandled',            // handler, optionally 'STATE.handler'
    	)
    ]
}

Aside from the pre-defined validators, you can also choose to define your own validator. All you need is a validate() function, which is the core function of all validators. The scope is the same as in your intents, so this will have the same functionality.

// validation
    
ExampleIntent: {
	input1: [
		{
			validate() {
				if(this.$inputs.input1.value === 'test') {
					return this.toIntent('Unhandled');
				}
			}
		}
	]
}

The same goes for assigning your own function.

// validation
    
ExampleIntent: {
	input1: function() {
		if(this.$inputs.input1.value === 'test') {
          	return this.toIntent('Unhandled');
		}
	}
}

This is just a quick overview of what we think could be achieved with this feature and how it could exist in the configuration file.

But we are eager to hear your opinions and ideas on this, so please feel free to comment on this topic! :slight_smile:


How do you Structure your Voice App Content?
#2

I have only used inputs on my pet project, which has not gotten much attention in a couple months, nor is it too complex. That being said, this idea sounds great and makes sense!


#3

Thanks Ruben, I really like this :raised_hands:

There are a few questions that I’d like to ask the community:

  • Do you have any examples were you remember doing “dirty workarounds” inside the app logic to handle NLU mismatches?
  • Would these examples be solved with the proposed feature by @rubenaeg?
  • What types of validation could you think of?
  • Is there anything we’re missing?

One thing I’m excited about is that you could potentially outsource some of the validations to a CMS, allowing people with no access to the code to do fixes:

// config.js
    
validation: {
    ExampleIntent: {
        input1: new ValidValuesValidator(this.$cms.validation.input1, 'Unhandled'),
    }
}

I also really like this article by Chas Sweeting and remember having an interesting conversation with @marktucker about it (where he proposed allowing people to require certain NPM packages as validatiors):


#4

This is actually great. But I think instead of creating custom validators, we could build a interface to be used with common validating libraries like joi. moleculer doest it in a very good and it work just fine.


#5

I’m a big fan of this work track, as I’ve manually doing this now in several places. A couple things jump out:

  1. Regex validation is also necessary.

  2. An async validator would be great if an input must be checked against a service.

  3. In addition to input validators by intent, how about a global registry based on entity? So if a given input is a Slot of type AMAZON.US_CITY or whatever, validation first happens through a global filter for all cities, then it hits the local intent validator.

  4. Stretch goal: While we’re in the domain of validation, how about a model-view (model-voice) abstraction that effectively pipes an input through a transform. So an input could be rejected/accepted via pass-fail (validation), but it could alternatively be transformed such that it passes a validation it would otherwise fail. E.g. phone number or address formatting.

Love the overall direction! I wish I had more time to contribute.


#6

Thanks @renatoalencar, will take a look.


#7

@jsmith6690 love these ideas, especially the global validation. Regarding your 4. point, if I understood you correctly this could be solved with the pre-defined validator ReplaceValuesValidator. It replaces certain values with a defined one. An example could look like this:

new ReplaceValuesValidator(['valueToReplace1, valueToReplace2'], 'newValue');

So whenever the input equals either valueToReplace1 or valueToReplace2, it would replace it with newValue.


#8

Ah, yes, I see now.

What about ReplaceValuesValidator optionally accepting a function rather than an array of values? Potentially an easy way to make the replacer highly robust without needing a ton of specific implementations.


#9

Could you ellaborate on that? According to the current design implementation you could already pass your own function as a way of validating your input your own way. This would even be possible in an array, so for example

// Validation

input1: [
    function() {
        let value = this.$inputs.input1.value;
        switch(value) {
            case 'test': value = 'anotherValue';
                break;
        }
    },
    function() {
        // Another validation function
    }
]

pinned globally #10