Cómo construir un bot de películas con SAP Conversational AI y NodeJS

Obtén recomendaciones de películas de The Movie Database preguntando a tu propio chatbot en Facebook Messenger.

Al final de este tutorial, podrá crear un robot de películas completamente funcional, capaz de hacer recomendaciones de películas basadas en varios criterios. Estamos utilizando la plataforma de creación de bots SAP Conversational AI (regístrese aquí gratis) y The Movie Database para obtener información sobre películas.

Aquí hay un chat de demostración con Movie Bot:

¿Qué estamos construyendo hoy?

La interacción con API de terceros permite casos de uso mucho más interesantes que los simples chatbots de preguntas y respuestas. Con Bot Skills , agregamos la opción de llamar a webhooks directamente desde el constructor, lo que lo hace aún más fácil.

El bot de hoy requiere varios pasos:

  1. Extraer información clave en una oración
  2. Construyendo el flujo del bot (disparadores, requisitos, acciones)
  3. Creación y conexión de una API de bot capaz de obtener datos de The Movie Database

Necesitará una cuenta de SAP Conversational AI, Node.JS y potencialmente Ngrok para las pruebas.

Antes de saltar, consulte esta guía si está buscando una guía que detalle la creación de su primer bot.

¡Hagámoslo!

Paso 1: extraer información clave de una oración

Las intenciones son útiles para determinar el significado general de una oración. Para nuestro caso de uso, saber que el usuario quiere ver algo no es suficiente.

Necesitamos saber qué quieren ver los usuarios.

Las entidades están diseñadas para resolver este problema: extraen información clave en una oración.

Las intenciones te hacen comprender que tienes que hacer algo. Las entidades te ayudan a hacer algo.

Imaginemos que es una empresa de telecomunicaciones que proporciona acceso a Internet y teléfono. Su bot tiene una intención que entiende cuando las personas se quejan de una interrupción:

Las entidades extraídas ayudarán a comprender qué va mal, dónde y desde cuándo .

Para nuestro robot de películas, intentaremos extraer 3 piezas clave de información :

  1. Qué quiere ver el usuario (una película frente a un programa de televisión)
  2. Que genero estan buscando
  3. En que idioma

Usando entidades de oro

Para ayudarlo a acelerar su desarrollo, SAP Conversational AI extrae varias entidades de forma predeterminada: fechas, ubicaciones, números de teléfono ...

Una lista exhaustiva está disponible aquí.

La Languageentidad será de ayuda:

¿Ves la pequeña estrella junto al nombre de la entidad? Diferencia una entidad dorada de una personalizada.

Lo usaremos para cumplir con nuestro tercer requisito: el idioma de la película.

Creando entidades personalizadas

Crearemos entidades personalizadas para extraer la información que necesitamos. Al igual que con las intenciones, el entrenamiento es muy importante: cuantos más ejemplos agregue a su bot, más preciso será.

El entrenamiento de sus entidades puede suceder a través de múltiples intentos. Las entidades son independientes de las intenciones.

Para nuestro bot de película, solo necesitamos una intención discover, y 2 entidades:

  • recordingpara identificar que el usuario quiere ver una película o un programa de televisión
  • genre

Abra la intención discovery agregue expresiones. Asegúrese de cubrir todas las posibilidades, esto significa una combinación saludable de expresiones con:

  • Ninguna entidad en absoluto: "Mi novio quiere ver algo esta noche"
  • Una entidad: "Quiero ver una película"
  • Muchas entidades: "¿Me pueden recomendar algunos programas de televisión de drama francés?"

Para etiquetar sus expresiones, seleccione el texto que desea etiquetar y escriba el nombre de su entidad:

Debería agregar muchos más ejemplos: 15 sería bueno, pero un bot listo para producción requeriría al menos 50 ejemplos para funcionar bien. Para acelerar el proceso, puede bifurcar las entidades creadas dentro de este bot [entidad de grabación, entidad de género] y luego bifurcar la intención de descubrimiento de este bot.

Puede ver aquí que se detectó el “francés” como una nacionalidad, no como un idioma porque eso es lo que es en este contexto. Al construir el flujo del bot, nos aseguraremos de verificar estas dos entidades.

Agregar enriquecimientos personalizados

¡Ahora que hemos etiquetado tener nuestras entidades las vamos a enriquecer! Abra el panel de entidades de su bot en la pestaña de entrenamiento como se muestra a continuación:

Now let’s open the genre entity. If you look at the top right of the panel you should see a toggle saying free - restricted and settings. Open it so we can explain in details the different options you have access to:

Within the entity panel you have access to different options for your entity:

  • Free vs Restricted — A free custom entity is used when you don’t have a strict list of values and want machine learning to detect all possible values. Whereas a restricted custom entity is used if you have a strict list of words to detect and don’t need automatic detection of the entity.
  • Fuzzy matching — Fuzzy matching is an index between 0 and 1 to indicate how close a word can be from the one in your entity list of values. If the word is above this index then the platform will tag it as the closest value within your list.
  • List of values — This is where you can add all the list of values of your entity which could be different values or synonyms

For more in-depth information about entities, you can read our detailed documentation.

In our case, our genre entity is going to be restricted as theMovie Database API only manages a specific list of genres. Here is the list below:

[ { id: 28, name: 'Action' }, { id: 12, name: 'Adventure' }, { id: 16, name: 'Animation' }, { id: 35, name: 'Comedy' }, { id: 80, name: 'Crime' }, { id: 99, name: 'Documentary' }, { id: 18, name: 'Drama' }, { id: 10751, name: 'Family' }, { id: 14, name: 'Fantasy' }, { id: 36, name: 'History' }, { id: 27, name: 'Horror' }, { id: 10402, name: 'Music' }, { id: 9648, name: 'Mystery' }, { id: 10749, name: 'Romance' }, { id: 878, name: 'Science Fiction' }, { id: 53, name: 'Thriller' }, { id: 10752, name: 'War' }, { id: 37, name: 'Western' } ]

Add all the different genres to our list of values. Don’t forget to also add synonyms such as SF, Sci-Fi for Science Fiction, Romantic for Romance or Animated, Cartoon for Animation. You can fetch the list of values from there.

As you can see from the JSON above, there are IDs associated with the genres. The reason is that the Movie Database can’t search for a specific genre based on its English name, but rather on a custom number. We can associate for each of the genre values a specific id that will be returned within the JSON of the NLP API. We can pass it on to the Movie Database API. This is the purpose of custom enrichments. Whenever an entity is detected, the JSON returned by the NLP API is enriched with additional information about the entity.

Within the custom enrichment panel we need to create 3 keys:

  • name – to map synonyms under the same value
  • id – to enrich with the id of the Movie Database
  • article – to add the article of the genre (we will use this later)

In order to add a custom enrichment click add new key and add the three keys listed above. For the article set the default key value to ‘a’ as most of the genres would be with ‘a’. Within name, you can start adding the specific enrichment and map it to all the different values for your article, id and name such as below:

You can fork the whole entity from this page which will include the enrichment. Now that this is done, let’s test it within the test console. If you send the sentence “I want to watch an animation movie” you now should now see the following custom enrichment:

"genre": [ { "value": "animated", "raw": "animated", "confidence": 0.99, "name": "animation", "id": 16, "article": "an" }

Great, now our enrichment gives us the generic name, id, and the article! Let’s do the same thing for the recording entity. Go back to the entities panel and click on recording. Then make it restricted and add all possible values and synonyms for tv show and movie (such as tv shows, shows, motion picture, film, films, movies, etc.). See the entire list here. Now go to custom enrichments and add the key type and add 2 specific values:

  • movie – for all movies synonyms
  • tv – for all tv shows synonyms

It should look like this:

Sending back our sentence “I want to watch an animation movie” we now also have the enrichment for recording:

"recording": [ { "value": "movie", "raw": "movie", "confidence": 0.99, "type": "movie" } ]

Step 2: Building your bot flow

Since we just need to make sure all our criteria are filled before calling a Node.JS API, the build part will be rather simple.

We will just need one skill, let’s call it discover.

You can find an example of a configured skill here.

Triggers

We want to trigger this skill if the intent @discover is present:

This tab helps you collect data before moving to Actions. We want to make sure the user specifies a recording, a genre, a language, and a yes or no intent before moving on:

The requirements will be checked one by one. They can all be fulfilled on the first message. For example, if the user says I want to watch a crime movie in English, then the Actions will be triggered immediately.

For each Requirement, you can choose to send a message if it is complete or if it is missing.

Sending messages when a requirement is complete can make your bot more lively: A crime movie? I love them too!, but are almost mandatory when the requirement is missing: You need to ask your users to fill what you need to know.

For example, I send quick replies with suggested genres if #genre is missing:

For the confirmation we are using the memory to display a dynamic message to validate the choice of the user using @yes and @no intent:

Once you have set up questions for the 4 groups of entities, go to the Actions tab.

Actions

Once the requirements are fulfilled, we want to call our API to actually perform the search if the user said yes. Else we reset the memory and ask again what the user wants to watch.

If _memory.no is present – reset the whole memory and send a message such as “Let’s start again, what do you want to watch?”

If _memory.yes is present create a CALL WEHBOOK action. You can either type a full URL (eg: //mydomainname.com/discover-movies), or a relative URL (/discover-movies). SAP Conversational AI will use the parameter Bot base URL in your bot settings when you type a relative URL.

Next, add an action UPDATE CONVERSATION > EDIT MEMORY > RESET ALL MEMORY to empty the memory once the call has been made.

If you don’t have a public server, or if you want to test your bot during development, ngrok is a very handy tool. It creates a public URL for you and forwards requests to your computer.

Once you installed it, run

ngrok http 5000

And copy the Forwarding URL in HTTPS (//XXX.ngrok.io) to your bot Settings (“Bot webhook base URL” field). All requests made to these URL will be forwarded to the port 5000 of your computer.

All your bot needs now are its API to get your movies!

Step 3: Creating the movie bot API

The NodeJS part of this bot is fairly simple: It will behave as an HTTP proxy between SAP Conversational AI and The Movie Database.

When your application receives a request from SAP Conversational AI, it sends a search query to the Movie Database with the criteria of your user and formats the JSON answer to the SAP Conversational AI message format.

Option 1: the automatic way

You can clone the entire project directly from our Git repository: //github.com/plieb/movie-bot-skills-training

Option 2: the manual way

Step 1 — scaffolding your project

mkdir movie-bot && cd movie-botnpm initnpm install --save express body-parser axiostouch index.js config.jsmkdir discover-movies && cd discover-moviestouch index.js movieApi.jscd..

Step 2— getting a TMDb API token

You will need a token to use the Movie Database API, go here to generate one, and edit your config.js file:

module.exports = ;

Step 3 — filling your index.js with an Express application

Let’s create an Express application to handle the requests from SAP Conversational AI. To better organize our project, as seen in Step 1, we have a folder /discover-movies/ which contains the core of our bot code (instead of putting all our files in the same folder), and we call it through loadMovieRoute.

const express = require('express');const bodyParser = require('body-parser');const config = require('./config');const loadMovieRoute = require('./discover-movies');const app = express();app.use(bodyParser.json());loadMovieRoute(app);app.post('/errors', function(req, res) { console.log(req.body); res.sendStatus(200);});const port = config.PORT;app.listen(port, function() { console.log(`App is listening on port ${port}`);});

Step 4 — filling discover-movies/index.js

We ask SAP Conversational AI to send a POST request to /discover-movies when a user has filled his search criteria.

The main goal of our controller is to pick and format the preferences from the memory to send them to the Movie Database’s API:

const config = require('../config'); const { discoverMovie } = require('./movieApi'); function loadMovieRoute(app) { app.post('/discover-movies', function(req, res) { console.log('[GET] /discover-movies'); const kind = req.body.conversation.memory['recording'].type; const genre = req.body.conversation.memory['genre'].id; const language = req.body.conversation.memory['language']; const nationality = req.body.conversation.memory['nationality']; const isoCode = language ? language.short.toLowerCase() : nationality.short.toLowerCase(); return discoverMovie(kind, genreId, isoCode) .then(function(carouselle) { res.json({ replies: carouselle, conversation: { } }); }) .catch(function(err) { console.error('movieApi::discoverMovie error: ', err); }); }); } module.exports = loadMovieRoute;

Step 5— filling discover-movies/movieApi.js

Now that we have extracted and formatted all the filters of the request, we need to send the request to the Movie Database and format the answer:

const axios = require('axios');const config = require('../config');function discoverMovie(kind, genreId, language) { return moviedbApiCall(kind, genreId, language).then(response => apiResultToCarousselle(response.data.results) );}function moviedbApiCall(kind, genreId, language) { return axios.get(`//api.themoviedb.org/3/discover/${kind}`, { params: { api_key: config.MOVIEDB_TOKEN, sort_by: 'popularity.desc', include_adult: false, with_genres: genreId, with_original_language: language, }, });}function apiResultToCarousselle(results) { if (results.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' }], }, }, ]; } const cards = results.slice(0, 10).map(e => ({ title: e.title || e.name, subtitle: e.overview, imageUrl: `//image.tmdb.org/t/p/w600_and_h900_bestv2${e.poster_path}`, buttons: [ { type: 'web_url', value: `//www.themoviedb.org/movie/${e.id}`, title: 'View More', }, ], })); return [ { type: 'text', content: "Here's what I found for you!", }, { type: 'carousel', content: cards }, ];}module.exports = { discoverMovie,};

Step 6 — Start the engine!

That’s all! You’re ready to test your bot.

Start your application by running: node index.js

All being well, you should see: App started on port 5000

Movie recommendations, weather, health, traffic… With third-party APIs, everything is possible! Now that you’re familiar with the workflow, we can’t wait to hear from you about what you’re building! And remember, you’re very welcome to contact us if you need help, trough the comment section below or via Slack.

Originally published on SAP Conversational AI blog.