Construire un bot de traduction vocale avec Twilio et IBM Watson

March 17, 2021
Rédigé par
Chisimdiri Ejinkeonye
Contributeur
Les opinions exprimées par les contributeurs de Twilio sont les leurs
Révisé par

Construire un bot de traduction vocale avec Twilio Programmable Voice et IBM Watson

Dans ce tutoriel, vous allez construire un bot de traduction vocale qui analyse votre voix lors d’un appel et répond avec une version traduite dans le langage de votre choix. La plupart des services de traduction vocale nécessitent une connexion internet. Cependant, le bot que vous allez construire n’a besoin que d’un réseau cellulaire et peut ainsi être aussi utilisé sur les téléphones qui n’ont que les fonctionnalités de base.

Pour construire ça, vous utiliserez la Programmable Voice de Twilio et l’API Speech Translation d’IBM Watson.

Conditions préalables

Pour être à même de suivre ce tutoriel, vous aurez besoin de :

  • Un compte API IBM Watson
  • Des connaissances en Node.js
  • Un compte Twilio gratuit
  • Un numéro de téléphone Twilio

Mise en place de Twilio

Commencez par vous connecter à votre compte Twilio. Si vous n’en avez pas encore, vous pouvez vous inscrire ici pour avoir un compte et un numéro d’essai gratuits (et si jamais vous décidez de passer à un compte payant, vous aurez un crédit Twilio de 10$!).

C’est quoi les webhooks ?

Pour recevoir des appels avec un numéro de téléphone Twilio, vous devez configurer un webhook. Ce sont des callbacks HTTP déclenchés par un évènement. Dans le cas d’aujourd’hui, l'événement serait un appel entrant sur votre numéro de téléphone Twilio.

Pour créer un webhook, vous avez besoin de créer un service REST capable de gérer les requêtes HTTP entrantes et d'exécuter quelques fonctions. Ce service aura besoin d’être accessible à partir d’un URL. C’est là que Twilio Functions entre en jeu.

Qu’est-que Twilio Functions ?

Twilio Functions est un environnement sans serveur, maintenu et développé par l'infrastructure cloud de Twilio. Avec ça, vous pouvez créer des webhooks pour répondre aux événements émis par Twilio. Pour cet exemple, vous utiliserez Twilio Functions pour rendre TwiML comme réponse aux webhooks.

Qu'est-ce que TwiML ?

TwiML, raccourci pour Twilio Markup Language, est une façon de donner des instructions à Twilio sur ce qu’il doit faire lorsqu’un événement est déclenché. Pour ce tutoriel, vous aurez besoin de répondre aux utilisateurs qui composent votre numéro de téléphone Twilio.

Par exemple, ce TwiML accueille en disant “hello” :

<Respond>
  <Say>Hello</Say>
</Respond>

Ce TwiML collecte les numéros saisis sur le clavier pendant un appel :

<Respond>
  <Gather input="dtmf"/>
</Respond>

Les commandes, après un verbe <Gather> ne s'exécutent pas si l’utilisateur appuie sur le clavier donc la commande suivante afficherait “Vous n’avez pas choisi de numéro” si l’utilisateur n’appuie pas sur le clavier.

<Respond>
  <Gather input="dtmf"/>
  <Say>You didn't pick a number</Say>
</Respond>

Le verbe <Gather> peut avoir des commandes imbriquées <Say>. Cela a pour effet de parler en attendant que l’utilisateur entre un numéro :

<Respond>
  <Gather input="dtmf">
    <Say>Press 1 for option 1</Say>
    <Say>Press 2 for option 2</Say>
  </Gather>
  <Say>You didn't pick a number</Say>
</Respond>

Vous apprendrez plus tard comment utiliser Twilio Functions afin de stocker le TwiML pour notre robot.

Mettre en place votre environnement de développement local

Pour développer Twilio Functions sur votre ordinateur local, vous aurez besoin d’installer le plugin Twilio Serverless Toolkit. D’abord, la CLI Twilio aura besoin d’être installée, suivie du plugin.

Voilà comment faire :  

npm install -g twilio-cli
twilio plugins:install @twilio-labs/plugin-serverless

Une fois que les packages sont installés avec succès, vous aurez besoin de vous connecter à votre compte Twilio via la CLI :

twilio login

Au moment de l’exécution, il vous sera demandé d’entrer vos Account SID (SID de compte) and Auth Token (jeton d’authentification). Vous les trouverez dans le tableau de bord de votre Console.

Une fois cela fait, créez un nouveau projet Twilio :

twilio serverless:init twilio-speech-translation-watson
cd twilio-speech-translation-watson

Cette commande va créer un nouveau répertoire, avec une structure fichier similaire à celle-ci :

├── assets
│ ├── index.html
│ ├── message.private.js
│ └── style.css
├── functions
│ ├── hello-world.js
│ ├── private-message.js
│ └── sms
│ └── reply.protected.js
└── package.json

Chaque fonction Twilio est un fichier *.js dans le dossier nommé functions. Ces fichiers peuvent être imbriqués de façon arbitraire sans soucis. 

Ensuite, installez la librairie Twilio npm :

npm install twilio

L’anatomie d’une fonction Twilio

Ouvrez le fichier functions/hello-world.js dans un éditeur de texte. Il devrait contenir ce qui suit :

exports.handler = function(context, event, callback) {
  const twiml = new Twilio.twiml.VoiceResponse();
  twiml.say('Hello World!');
  callback(null, twiml);
};

Chaque fonction Twilio exporte une fonction handler, qui accepte trois arguments : context, event et callback. Pour le moment, celui sur lequel il faut se concentrer, c’est l’argument callback. C’est une fonction callback pour répondre et terminer une exécution de Twilio Functions. Son premier paramètre est un message d’erreur, tandis que le deuxième est une réponse.

La réponse, dans ce cas, est un verbe <Say> TwiML, qui dit à Twilio de parler à l’utilisateur. Mis à part le verbe <Say>, vous utiliserez aussi le verbe <Gather> pour collecter les saisies de l’utilisateur, ainsi que le verbe <Redirect> pour transférer le contrôle de l’appel à un autre Twilio Function.

Mettre en place IBM Watson

Vous avez fini de paramétrer Twilio. Maintenant, vous devez obtenir l’accès à l’API de traduction d’IBM Watson. Watson a un plan gratuit, ce qui est parfait pour ce tutoriel. Inscrivez-vous ici, ou connectez-vous si vous avez déjà un compte.

Ensuite, direction le Traducteur de Langage Cloud d’IBM. Sélectionnez le plan Lite gratuit et créez un nouveau service.

plan de pricing Watson

Vous serez redirigés sur la page de service du Language Translator (Traducteur de Langage). Sélectionnez Service credentials à partir du menu de gauche.

capture d&#x27;écran du tableau de bord du traducteur de langue

Copiez la apiKey à partir de la liste déroulante Auto-generated service credentials (identifiants de service auto-générés)

Retournez maintenant dans le dossier de votre projet et ouvrez votre fichier .env. Sur une nouvelle ligne, ajoutez une variable d’environnement appelée WATSON_KEY avec la valeur de la clé API que vous venez d'obtenir.

WATSON_KEY=<YourApiKey>

Copiez maintenant le champ url, aussi à partir de la liste déroulante Auto-generated service credentials. Gardez cet URL à portée de main, vous l’utiliserez plus tard.

Cela fait, vous avez fini de paramétrer le service Watson Translate.

Structure du Speech Translation Service (Service de Traduction Vocale)

Le service de traduction que vous construisez possède trois fonctions :

  1. greeting.js - Accueille l’utilisateur et lui demande de sélectionner un langage. Cela donne le contrôle de l’appel à handle-language.js.
  2. handle-language.js - Vérifie si la langue saisie est bien supportée par l’API Watson. Si elle l’est, alors l’utilisateur est invité à dire le message qu’il souhaite traduire. Puis le contrôle de l’appel est passé à translate-message.js.
  3. translate-message.js - Vérifie si le message à traduire est valable. Si oui, il appelle l’API Watson et énonce le message traduit. C’est la fonction finale.

Les parties suivantes vous apprendront comment créer ces fonctions.

Répondre aux appels entrants

Créer un fichier greeting.js

Créez un fichier greeting.js dans le dossier functions.Ce fichier va contenir le gestionnaire, déclenché lorsqu’un appel commence :

touch ./functions/greeting.js

Windows:

type nul > ./functions/greeting.js

Copiez et collez le code suivant dans ce nouveau fichier :

const VoiceResponse = require('twilio').twiml.VoiceResponse;

const voiceConfig = {
  voice: "Polly.Amy-Neural"
}

const supportedLanguages = ['german', 'french', 'japanese'];

exports.handler = function(context, event, callback) {
  const twiml = new VoiceResponse();
  const gather = twiml.gather({
    finishOnKey: '',
    action: '/handle-language'
  });

  gather.say(voiceConfig, "What language do you want to translate to ?");

  supportedLanguages.forEach((language, index) => {
    gather.say(voiceConfig, `Select ${index + 1} to translate to ${language}`);
  });

  callback(null, twiml);
};

La fonction gather() reçoit un objet comme un argument avec les détails qu’il utilise pour construire un verbe TwiML <Gather>.

Ces arguments sont :

  • finishOnKey: Indique à Twilio d’arrêter d’attendre la saisie de l’utilisateur et de continuer le flux de l’appel.
  • action: Indique à Twilio de transférer le flux de l’appel au endpoint (point de terminaison) spécifié. Remarquez que vous utilisez un URL relatif. Cette décision rend votre code plus dynamique pour différents environnements… Twilio utilise le domaine dans lequel la fonction est située.

La fonction say() possède deux arguments. Le premier est un objet voiceConfig qui vous permet de choisir la voix du narrateur. Le deuxième est le message qui doit être narré.

Remarquez comment vous faites une loop dans chaque langage dans le tableau supportedLanguages, construisant un nouveau verbe <Say> TwiML pour chaque langage.

Lorsque l’appelant entre un numéro avec le clavier, Twilio appelle le point de terminaison /handle-language avec une valeur de requête Digits contenant la saisie numérique de l’appelant.

NOTE : Si le ton de la voix n’est pas à votre goût, allez voir la liste des voix supportées par Twilio.

Gérer le langage de saisie de l’utilisateur

Maintenant que vous avez collecté l’entrée du texte de l’utilisateur, lors du point de terminaison précédent  /greeting, vous allez ordonner à Twilio de <Say> l’entrée du langage de l’appelant.

Créez un nouveau fichier appelé handle-language.js: 

 

touch ./functions/handle-language.js

Windows:

type nul > ./functions/handle-language.js

Copiez et collez le code suivant dans ce fichier :

const VoiceResponse = require('twilio').twiml.VoiceResponse;

const languageOptions = {
  "1": {
    language: "german",
    voiceCode: "de-DE"
  },
  "2": {
    language: "french",
    voiceCode: "fr-FR"
  },
  "3": {
    language: "japanese",
    voiceCode: "ja-JP"
  }
}

exports.handler = async function(context, event, callback) {
  const twiml = new VoiceResponse();
  const digit = event.Digits;

  if (!languageOptions[digit]) {
    twiml.say("You didn't say anything.");
  } else {
    const {
      language,
      voiceCode
    } = languageOptions[digit];

    twiml.say(`Great! Your words will be translated to ${language}`)
  }
  callback(null, twiml);
};

La réponse de l’appelant, à partir du point de terminaison /greetings est accessible depuis event.Digits.

Point de contrôle

Regardons un peu ce que vous avez jusqu’ici. Déployez les fonctions avec la commande suivante :

twilio serverless:deploy

Rendez-vous dans la partie Phone Number de la Console Twilio :

capture d&#x27;écran de la page de gestion des numéros de téléphone sur la console twilio

Achetez le numéro de téléphone désiré (ou sélectionnez celui attribué pour l’essai), et sur la page de configuration du numéro de téléphone, descendez jusqu’à la partie Voice & Fax :

Webhook de Voice & Fax

Sélectionnez Function à partir du menu déroulant A call comes in (un appel entrant):

liste déroulante du gestionnaire d&#x27;événements twilio

Mettez à jour les options restantes comme dans l’image suivante :

liste déroulante du gestionnaire d&#x27;événements twilio

N’oubliez pas de sauvegarder.

Essayez maintenant d’appeler le numéro de téléphone. Si vous avez tout fait correctement, vous devriez avoir une invite vous demandant d’entrer le langage cible de traduction. Après ça, vous devriez aussi entendre un message de confirmation réaffirmant le langage choisi.

Si cela ne fonctionne pas, retournez vérifier les choses suivantes :

  1. Le code que vous avez entré pour les diverses fonctions
  2. L’URL de point de terminaison que vous avez saisi dans “Un appel entrant”

Valider la saisie de langage de l’utilisateur

Ensuite, vérifiez si le langage cible sélectionné par l’utilisateur est supporté. Vous utiliserez la librairie ibm-watson pour requêter l’API de langage de Watson.

Installez la librairie npm ibm-watson:

npm install ibm-watson

Mettez à jour le fichier handle-language.js pour importer ibm-watson en ajoutant les deux lignes suivantes tout en haut du fichier :

const LanguageTranslatorV3 = require('ibm-watson/language-translator/v3');
const { IamAuthenticator } = require('ibm-watson/auth');

Créez ensuite les fonctions pour utiliser la librairie que vous venez juste d’importer. Ajoutez ces fonctions en bas du fichier handle-language.js :

async function getLanguageCode(language, apikey) {
  const supportedLanguages = await getSupportedLanguages(apikey);
  const desiredLanguage = supportedLanguages.find(lan => lan.language_name.toLowerCase() === language);
  return desiredLanguage.language;
}

async function getSupportedLanguages(apikey) {
  try {
    const languageTranslator = new LanguageTranslatorV3({
      version: '2018-05-01',
      authenticator: new IamAuthenticator({
          apikey,
      }),
      serviceUrl: 'YOUR_SERVICE_URL',
    });

    const languages = (await languageTranslator.listLanguages()).result.languages;
    return languages;
  } catch (error) {
    console.log(error);
    throw 'Issue getting languages';
  }
}

Vous vous rappelez de l’URL que vous avez copiée de la page de service Watson ? Remplacez la valeur du serviceURL ci-dessus par votre URL. Abrégez l’URL pour ne pas inclure la suite après “ibm.com”.

Par exemple si votre URL est :

https://api.eu-gb.language-translator.watson.cloud.ibm.com/instances/af3dasf833 

Vous devez la raccourcir comme ceci :

https://api.eu-gb.language-translator.watson.cloud.ibm.com

Remplacez la fonction exports.handler dans ce fichier avec le code suivant, dans le but de se servir de nos nouvelles fonction :

exports.handler = async function(context, event, callback) {
  const twiml = new VoiceResponse();
  const digit = event.Digits;

  if (!languageOptions[digit]) {
    twiml.say("You didn't say anything.");
  } else {
    try {
      const {
        language,
        voiceCode
      } = languageOptions[digit];

      const languageCode = await getLanguageCode(language, context.WATSON_KEY);

      if (languageCode) {
        twiml.gather({
          input: ['speech'],
          action: `/translate-message?languageCode=${languageCode}&voiceCode=${voiceCode}`
        })
        .say(`Great! Your words will be translated to ${language}, What message do you want to translate ?`);
      } else {
        twiml.say("Sorry, language not supported");
        twiml.redirect('/greeting');
      }
    } catch (error) {
      console.log(error);
      twiml.say("There was an issue getting supported languages");
    }
  }

  callback(null, twiml);
};

Notez que vous passez context.WATSON_KEY à la fonction getLanguageCode(). Comme vous l’avez peut-être deviné, l’argument context est un objet dans les variables d’environnement que vous avez définies dans le fichier “.env”.

Lorsqu’un langage est supporté, vous réaffirmez la saisie de langage de l’utilisateur avec le verbe <Say>. Vous demandez ensuite à l’utilisateur de dire le message qu’il souhaite traduire en utilisant le verbe <Gather>. Et enfin, vous redirigez le flux de conversation pour qu’il soit géré par le point de terminaison /translate-message.

Regardons un peu la fonction gather().

twiml.gather({
  input: ['speech'],
  action: `/translate-message?languageCode=${languageCode}&voiceCode=${voiceCode}`
});

Le point de terminaison d’action que vous spécifiez possède deux paramètres de requêtes :  languageCode et voiceCode. Vous avez besoin de languageCode pour spécifier le langage cible de l’API Watson Translate. Ensuite, vous avez besoin de voiceCode pour spécifier la voix du narrateur Twilio.

Lorsque les paramètres de requêtes sont entrés dans un point de terminaison Twilio, il est exposé sous l’argument de fonction d'événement pour la fonction translate-message.js.

Traduire le message et répondre

Créer un fichier translate-message.js avec la commande suivante :

touch ./functions/translate-message.js

Windows:

type nul > ./functions/translate-message.js

Ajoutez le contenu suivant :

const VoiceResponse = require('twilio').twiml.VoiceResponse

exports.handler = function (context, event, callback) {
  const twiml = new VoiceResponse();
  const speechResult = event.SpeechResult;
  twiml.say("You said, " + speechResult);

  callback(null, twiml);
};

Cela répète simplement ce que l’utilisateur dit dans l’étape d’avant. Twilio expose la retranscription de la saisie vocale de l’utilisateur au point de terminaison précédent dans event.SpeechResult.

Vous êtes maintenant prêts à déployer votre code et à le tester pour vous assurer que tout fonctionne correctement jusqu’ici.

twilio serverless:deploy

Il est maintenant l’heure de faire en sorte que la fonction traduise la réponse vocale et qu’elle puisse la transmettre à l’appelant.

Remplacez tout le code dans translate-message.js par le suivant :

const VoiceResponse = require('twilio').twiml.VoiceResponse;
const LanguageTranslatorV3 = require('ibm-watson/language-translator/v3');
const { IamAuthenticator } = require('ibm-watson/auth');

exports.handler = async function (context, event, callback) {
  const twiml = new VoiceResponse();
  const speechResult = event.SpeechResult;
  const languageCode = event.languageCode; 
  const voiceCode = event.voiceCode;

  const translatedSpeech = await translateSpeechResult(speechResult, languageCode, context.WATSON_KEY);

  if (!translatedSpeech) {
    twiml.say("Could not translate");
    callback(null, twiml);
  }
  
  twiml.say(`${speechResult} means`);
  twiml.say({ language: voiceCode }, `${translatedSpeech}`);
  callback(null, twiml);
};

async function translateSpeechResult(speechResult, targetLanguageCode, apikey) {
  try {
    const languageTranslator = new LanguageTranslatorV3({
      version: '2018-05-01',
      authenticator: new IamAuthenticator({
        apikey,
      }),
      serviceUrl:'<watson-url>',
    });

    const result = (await languageTranslator.translate({
      text: speechResult,
      target: targetLanguageCode
    })).result;

    return result.translations[0].translation;

  } catch (err) {
    console.log(err);
    return '';
  }
} 

Rappelez-vous de remplacer l’espace réservé <watson-url> par l’URL que vous avez noté dans le tableau de bord IBM Watson.

Voici un aperçu de ce qui se passe :

D’abord, vous obtenez les paramètres de requête voiceCode et languageCode de l’objet d’événements. Souvenez-vous, la fonction précédente a passé ces paramètres.

Passez deux attributs à l’argument d’objet de la fonction translate:

  1. text: Le texte à traduire
  2. target: Le langage cible

NOTE : Allez voir la documentation Watson Translate pour plus de détails sur les arguments supportés.

Une fois fait, déployez votre code une dernière fois :

twilio serverless:deploy

Essayez d’appeler le numéro de téléphone. Tout devrait marcher. Si ce n’est pas le cas, essayez de re-vérifier étape par étape, ou bien allez voir le code source.

Conclusion

Dans ce tutoriel, vous avez appris à construire un simple bot de traduction vocale. Vous avez utilisé TwiML qui donne les instructions à Twilio sur comment gérer les appels entrants et les requêtes callback. Vous avez aussi appris comment marche une fonction Twilio et les contenus de ses nombreux arguments. Et enfin, vous avez appris comment appeler l’API Watson Translate avec la librairie npm watson-api.

Si vous êtes bloqués, voici le code source :

https://github.com/scroobius-pip/twilio-speech-translation-watson-tutorial

Pour plus détails sur les fonctions Twilio, allez voir :

https://www.twilio.com/docs/runtime/functions/invocation

Plus plus de détails sur l’API Translate, allez voir :

https://cloud.ibm.com/apidocs/language-translator?code=node#translate

Chisimdiri (ou Simdi) est un développeur software, mais se considère avant tout comme un créateur. Il aime construire des solutions concrètes à ses problèmes, et parfois les partager avec le public. Lorsque Simdi n’est pas en train de coder, il écrit sur ses grandes idées, il lit et regarde des animés.

Suivez-le sur :