Routing
Learn more about the different elements of routing: How the Jovo app decides which component and handler should respond to a request.
Introduction
Routing is a key activity of dialogue management (as part of the RIDR Lifecycle) and is responsible for matching user input with handlers and components that can return a appropriate response.
There are multiple types of routing:
- Request routing: This is the initial routing that happens for each request. It results in an initial handler that is being selected by the router and then executed by Jovo.
- Handler routing: Often, handlers route to other handlers or components during the same interaction.
Request Routing
The router goes through all handlers that potentially match the request (e.g. that accept a specific intent
) and collects them in a $route
object. It takes into consideration the following handlers:
- Handlers from all components that are part of the
$state
stack - Global handlers from all root components
The results are added to a matches
object in the $route
. In a below section, we go into detail how handler matches work.
The matches are ranked by priority. The currently active component in the $state
stack is the most important one, then the next components in the stack follow, until global handlers are reached. Learn more about handler and component prioritization in the section below.
Here's an example how the matches
look like for a request with a YesIntent
. This uses the Jovo v4 sample template as an example.
this.$route = { // ... matches: [ { component: 'LoveHatePizzaComponent.YesNoComponent', handler: 'yes', }, { component: 'LoveHatePizzaComponent.YesNoComponent', handler: 'UNHANDLED', }, { component: 'LoveHatePizzaComponent', handler: 'UNHANDLED', }, ], };
In the example above, the the yes
handler is the best match that ranks above two UNHANDLED
handlers. The router then selects the highest ranked match and adds it to a resolved
object:
this.$route = { resolved: { { component: 'LoveHatePizzaComponent.YesNoComponent', handler: 'yes' }, } matches: [ // ... ] }
After resolved
is determined, Jovo runs the specified handler.
Handler Matches
To find handlers that could potentially fulfill a request, the router first goes through the $state
stack (for local and global handlers) and then through all root components (for global handlers).
If a handler matches a request is highly dependent on the metadata that is added using the @Handle
decorator (or convenience decorators like @Intents
). Learn more about these decorators here.
A handler counts as match if:
- It matches a specific request type, like
LAUNCH
- For raw text or intent request types: It accepts the incoming
intent
(and potentiallysubState
) - It also matches other
@Handle
conditions likeplatforms
andif
- It is an
UNHANDLED
handler (more on that below)
Let's take another look at our example from above. If there is a request with a YesIntent
, the LoveHatePizzaComponent.YesNoComponent
is in the $state
stack, and contains a handler like this:
@Intents([ 'YesIntent' ]) yes() { // ... }
Then this matches and the handler is added to the $route.matches
:
this.$route = { // ... matches: [ { component: 'LoveHatePizzaComponent.YesNoComponent', handler: 'yes', }, // ... ], };
If a handler is global
and has the prioritizedOverUnhandled
property, this info is added to the match item as well:
this.$route = { // ... matches: [ { component: 'SomeComponent', handler: 'SomeHandler', global: true, prioritizedOverUnhandled: true, }, // ... ], };
Handler and Component Prioritization
The ranking is determined between components, handlers of a component, and global handlers.
For each component, the handlers are ranked in the following order:
- Handlers using the
if
property + other conditions (e.g.platforms
). The more conditions, the higher the ranking. - Handlers using only the
if
property. - Handlers using other conditions (e.g.
platforms
). The more conditions, the higher the ranking. - Handlers using no other conditions.
UNHANDLED
handlers.
At the component-level, the ranking is as follows:
- The most active component of the
$state
stack is ranked highest. - After that, each lower component in the stack is ranked lower as well.
- If there are no more components in the
$state
stack, global handlers are ranked at the lowest level.
UNHANDLED Prioritization
UNHANDLED
is a built in handler that can be seen as a wildcard. If the active component doesn't have a handler that matches the request, then UNHANDLED
can be used as fallback handler. Learn more about UNHANDLED
in the handler docs.
UNHANDLED
is always ranked below all other handlers from the same component:
this.$route = { // ... matches: [ { component: 'LoveHatePizzaComponent.YesNoComponent', handler: 'yes', }, { component: 'LoveHatePizzaComponent.YesNoComponent', handler: 'UNHANDLED', }, // ... ], };
Often, there could be the case that the UNHANDLED
handler is on top of the matches
. In the below case, the user might have asked for business hours instead of responding to a yes/no question. If the active component has an UNHANDLED
handler, this would always be at the top in a case like this:
this.$route = { // ... matches: [ { component: 'LoveHatePizzaComponent.YesNoComponent', handler: 'UNHANDLED', }, { component: 'BusinessDataComponent', handler: 'businessHours', }, // ... ], };
This could mean that the user gets stuck in a loop: No matter what they say, if it's not "yes" or "no", they will always end up in UNHANDLED
, which can be a frustrating experience.
For this, there are two options that make it possible to skip UNHANDLED
:
prioritizedOverUnhandled
There might be some handlers where you decide that they are more important than UNHANDLED
, even if they're ranked below it in the matches
. You can highlight them as prioritized by adding the prioritizedOverUnhandled
property. Learn more about prioritizedOverUnhandled
in the handler docs.
If a handler is prioritized, the property gets added to the matches
element:
this.$route = { // ... matches: [ { component: 'SomeComponent', handler: 'SomeHandler', prioritizedOverUnhandled: true, }, // ... ], };
In the process of finding the best ranked handler, the router goes through the matches
and adjusts the prioritization with the following steps:
- Search for any
UNHANDLED
inmatches
. - For each
UNHANDLED
, look if there is a handler withprioritizedOverUnhandled
ranked somewhere below. - If there is a
prioritizedOverUnhandled
handler below (if multiple, select the highest ranked one), addskip: true
to theUNHANDLED
handler. - Add
skip: true
to any other handlers betweenUNHANDLED
and the firstprioritizedOverUnhandled
handler below it.
Here is an example that would result in skipped UNHANDLED
:
this.$route = { // ... matches: [ { component: 'LoveHatePizzaComponent.YesNoComponent', handler: 'UNHANDLED' skip: true, }, { component: 'LoveHatePizzaComponent', handler: 'help' skip: true, }, { component: 'BusinessDataComponent', handler: 'businessHours', prioritizedOverUnhandled: true, }, // ... ] }
In this example, the router would resolve to this:
this.$route = { resolved: { component: 'BusinessDataComponent', handler: 'businessHours', prioritizedOverUnhandled: true, }, // ... };
intentsToSkipUnhandled
As opposed to prioritizedOverUnhandled
, a property that takes the perspective of a specific handler, the intentsToSkipUnhandled
configuration is global. It defines all intents that that should completely ignore UNHANDLED
handlers.
Let's assume we have a BusinessHoursIntent
that is part of intentsToSkipUnhandled
. If there is a request with that intent, the router adds skip: true
to all UNHANDLED
handlers in matches
.
this.$route = { // ... matches: [ { component: 'LoveHatePizzaComponent.YesNoComponent', handler: 'UNHANDLED' skip: true, }, { component: 'LoveHatePizzaComponent', handler: 'help' }, { component: 'BusinessDataComponent', handler: 'businessHours', prioritizedOverUnhandled: true, }, // ... ] }
Note the difference between this example and the one from prioritizedOverUnhandled
. Here, help
(a handler that apparently also accepts BusinessHoursIntent
) doesn't get skipped. Instead, it gets added to resolved
and is the handler to be routed to.
this.$route = { resolved: { component: 'LoveHatePizzaComponent', handler: 'help', }, // ... };
Handler Routing
Handler routing is what happens after the initial request routing. There are three options how a handler could invoke a different one: