Start building your own chatbot now >

Disclaimer

This tutorial focuses on  explaining what Random Access Navigation (RAN) is for bots, rather than detailing the classical (if that exists) bot building process in itself. You’ll also learn how to implement it within your Recast.AI bot and your code in Node JS. You can find the GitHub repository here and the Recast.AI bot associated with the project here. Let’s begin!

What is Random Access Navigation (RAN) ?

Random Access Navigation, usually shortened RAN, was first assessed by Shane Mac of Assist on a Medium post. It is the process of allowing people to navigate through a bot based on entities more than intents (if those notions are unclear to you, you can take a look at this article). Let’s take an example (that we’ll use throughout our tutorial): Movie Bot (moviedbot).

Let’s say you want to watch a movie tonight and you want to discover a new one. You can actually browse movies by years, genres or even language/country of release. That said, if we wanted to build this bot intent based, we’d need four intents : get-typeget-date, get-genre, get-language. The flow will probably look like this :

Flow Recast.AI Movie Bot

Flow Movie Bot

Going from greeting to get-language in a linear flow would force the user to answer one question after the other and move forward within our pre-determined flow. As you see here, we could run through different issues using this type of flow:

  • Asking first “I’d like to find a romance movie”
  • Giving away two options “Can you get me some Science Fiction movies released in 2016?”
  • Changing one of our option “Sorry I meant 2015”

You’ve probably caught my drift by now. An intent based flow doesn’t solve our movie bot issues in this particular case. What we need is a bot that understands that we need a year, a genre, a language or a country: entities. This, regardless of the order of entities or combination of entities such as year and genre and therefore ask for the missing one.

The goal is to make your bot detects all entities required to perform an action. This process allows the user to change their mind without going back in the flow, which would complicate the logic. That way, you can make your bot smarter and quicker to provide the correct information.

Now you’re wondering “Ok I get it, but how can I do this using Recast.AI?”.

How to build your RAN bot with Recast.AI ?

Building the Movie Bot within Recast.AI is fairly easy. Start by creating a new bot by forking the talking bot. In order to make our bot entity based we just create one intent: discover. To make the request to the movie API database, our bot needs to know a year, a genre, a nationality – country – language and if it’s a movie or a TV show. Lucky for us, Recast.AI already knows what a datetime, a language, a location for the country and nationality is. We just need to teach the bot to understand the different genres and the difference between a movie and a TV show.

Open the intent discover and add expressions :

  • some with no entities at all : “My boyfriend wants to watch a new movie”
  • some with one entity : “Any good movies release in 2014?”
  • some with many entities : “Can you recommend me some French drama TV shows?”

See below for some examples :

Expressions RAN

Expression – Discover

Now we need to tag drama, romance or any other genres you want as custom entities. Once you tagged a few go back to your bot page and scroll down to find your new genre gazette. Click and add the other genres. For the purpose of this bot we are using the genres the Movie DB API provide for our entities to match directly with their genres database. You can find the full list of the Movie DB here. Close your gazette to make sure only the entities within your gazette will match as show below :

Genre gazette tutorial RAN

Genre gazette

Now, let’s do the same for movie and TV show which are going to be standalone entities. In your gazette, provide synonyms for movie (film, movies, etc.) same apply for TV shows (shows, sitcom, tv, etc.). Click on the links to check our gazettes for movies and TV shows.

Tutorial Movie TV Entity

Movie & TV entity

Now that the training part is done, head to the Bot builder. As we just have one intent the flow is very simple, just add a greeting action and your discover action to the builder. You can add all the other intents forked from the talking bot to provide small talk intents to your customers. This is how your builder should look :

Builder RAN tutorial

Bot Builder

Now we need to update our action discover to set notions : (genre) AND (movie OR TV) AND (datetime) AND (location OR language OR nationality) as shown below :

notions RAN tutorial

Notions

Now that we’re done with the Bot Builder, head for the bot connector and connect your bot to Messenger

How to manage RAN within your code ?

Our bot is now up and running with Recast.AI. The challenge now is to manage RAN through the code. The way it works is we always call the flow (Bot Builder). This means there are only two things the bot does: either small talk or looking for a movie.

First, start by cloning the Recast.AI node.js starter kit, available on Github:

git clone https://github.com/RecastAI/starter-NodeJS.git nodejs-ran-bot
cd nodejs-ran-bot
npm install

Then, update the function replyMessage from src/message.js :

const replyMessage = (message) => {
 // Instantiate Recast.AI SDK, just for request service
 const request = new recastai.request(process.env.REQUEST_TOKEN, process.env.LANGUAGE)

 // Get text from message received
 const text = message.content
 // Get senderId to catch unique conversation_token
 const senderId = message.senderId

 // Call Recast.AI SDK, through /converse route
 request.converseText(text, { conversationToken: senderId })
   .then(conversation => {
     if (conversation.action) {
       if (conversation.action.slug === 'discover') {
         return startSearchFlow(message, conversation)
       }

       // We sometime want to reset the memory on some intents
       if (conversation.action.slug === 'greetings' || conversation.action.slug === 'reset') {
         conversation.resetMemory()
          .then[1]) => console.log('Memory has been reset'
       }

       if (conversation.replies.length > 0) {
         // Add each reply received from API to replies stack
         conversation.replies.forEach(replyContent => message.addReply({ type: 'text', content: replyContent }))
       } else {
        // If there is not any message return by Recast.AI for this current conversation
        message.addReply({ type: 'text', content: 'I don\'t have the reply to this yet :)' })
       }
       // Send all replies
       return message.reply()
        .catch(err => {
          console.error('Error while sending message to channel', err)
        })
    }
    // If we don't have an intent for this message, we just want to go through to flow, maybe we'll find some interesting entities!
    // In any case, the user will receive quick replies and it will help him get back on tracks
      return startSearchFlow(message, conversation)
    })
 .catch(err => {
   console.error('Error while sending message to Recast.AI', err)
 })
}

Our main function, called startSearchFlow, starts by getting all the notions we setup on the Bot Builder from the API result .

const startSearchFlow = (message, conversation) => { 
    const movie = conversation.getMemory('movie') 
    const tv = conversation.getMemory('tv') 
    const genre = conversation.getMemory('genre') 
    const date = conversation.getMemory('datetime') 
    const nationality = conversation.getMemory('nationality') 
    const language = conversation.getMemory('language') 
}

Now, check which notions are set, so we can ask the user for the missing ones we need to make a request to The Movie DB API :

if (!genre) {
    return message.reply([{
        type: 'quickReplies',
            content: {
            title: 'What genre of movies do you like?',
            buttons: [
                { title: 'Action', value: 'Action' },
                { title: 'Comedy', value: 'Comedy' },
                { title: 'Drama', value: 'Drama' },
                { title: 'Family', value: 'Family' },
                { title: 'History', value: 'History' },
                { title: 'Horror', value: 'Horror' },
                { title: 'Romance', value: 'Romance' },
             ],
        },
    }])
 }
 if (!date) {
    return message.reply([{ type: 'text', content: 'What year of release?' }])
 }
 if (!nationality && !language) {
    return message.reply([{ type: 'text', content: 'What language?' }])
 }

Whether all conditions but one are fulfilled on the first request, or one by message after message, we’ll go through our conditions until we have everything we need. The Bot Builder will take care of the memory management, so we don’t need a database to keep track of the entities over multiple requests.
Once we have collected all information we need, we just have to send a request to themoviedb’s api (you can find the documentation for the endpoint we’ll be using here).

We’ll be using axios, an handy Promise based http client.
Don’t forget to run npm install --save axios !

Let’s write these functions in a separate file, called src/movieApi.js :

const axios = require('axios')

const moviedbApiCall = (kind, params) => {
   return axios.get(`https://api.themoviedb.org/3/discover/${kind}`,{
     params: Object.assign({}, {
       api_key: process.env.MOVIEDB_TOKEN,
       sort_by: 'popularity.desc',
       include_adult: false,
     }, params),
   }).then(res => res.data.results)
}

const discoverMovie = ({ genreId, isoCode, year }) => {
  return moviedbApiCall('movie', {
    with_genres: genreId,
    primary_release_year: year,
    with_original_language: isoCode,
  })
   .then[2]response) => apiResultToCarousselle(response, 'movie'
}

const discoverTv = ({ genreId, isoCode, year }) => {
  return moviedbApiCall('tv', {
    with_genres: genreId,
    first_air_date_year: year,
    with_original_language: isoCode,
  })
   .then[3]response) => apiResultToCarousselle(response, 'tv'
}

module.exports = { discoverMovie, discoverTv, }

Finally, we format the result in a nice carousel :

const apiResultToCarousselle = (results, kind) => {
  const cards = results.slice(0, 10)
    .map(e => ({
      title: e.title || e.name,
      subtitle: e.overview,
      imageUrl: `https://image.tmdb.org/t/p/w640${e.poster_path}`,
      buttons: [{
        type: 'web_url',
        value: `https://www.themoviedb.org/${kind}/${e.id}`,
        title: 'View More',
      }],
    }))

  if (cards.length === 0) {
    return [{
      type: 'quickReplies',
      content: {
        title: 'Sorry, but I could not find any results for your request :(',
        buttons: [{ title: 'Start over', value: 'Start over' }],
      },
    }]
  }

 return [
    { type: 'text', content: 'Grab some pop corn and enjoy:' },
    { type: 'carouselle', content: cards }
  ]
}

At the end of our function startSearchFlow, in src/messages.js, we just have to call the API and send the results :

if (movie) {
  return movieApi.discoverMovie({ genreId, isoCode, year })
    .then(carouselle => message.reply(carouselle))
}
return movieApi.discoverTv({ genreId, isoCode, year })
  .then(carouselle => message.reply(carouselle))

And tadaaaaa!

There are many ways to improve this bot: we could handle more types entities, allow users to skip a question if they don’t care about a criteria or make our bot more lively by adding some randomness to its wording.

We already tried to implement this, so go checkout the source code on Github. We would be thrilled to see what improvements you could make! You can also use the bot on Messenger 🙂

We hope you enjoyed this RAN tutorial, if you have any feedback, leave a comment !

References   [ + ]

1. ) => console.log('Memory has been reset'
2. response) => apiResultToCarousselle(response, 'movie'
3. response) => apiResultToCarousselle(response, 'tv'

Want to build your own conversational bot? Get started with Recast.AI !

Subscribe to our newsletter


  • Anonymous

    Nathan, super article! Aurais-tu le temps pour une demo, je serai ravi de faire ta connaissance et de collaborer avec toi.

  • Todd

    This looks awesome!!