Sviluppare un chatbot multilingue per ordinare il caffè tramite WhatsApp

May 19, 2020
Written by
Reviewed by

whatsapp-coffee-banner-it.png

Quest'articolo è la continuazione del precedente interessantissimo articolo, in cui Dominik descrive come costruire un sistema per ordinare caffè usando Autopilot. Al momento, Twilio Autopilot supporta solo la lingua inglese, esploreremo quindi come implementare il sistema per ricevere ordini sia in inglese che in italiano.

A summary of this article in english, along with a one click deploy script is available at this repo on GitHub.

Prerequisiti

Se non avete ancora il vostro account Twilio, seguite questo link per crearne uno gratuito. Ci vogliono meno di 2 minuti!

Nel vostro account Twilio useremo:

Inoltre, avrete bisogno di un account per Google DialogFlow e Google Cloud platform.

Diagramma di funzionamento

Il flusso che andremo ad implementare è il seguente:

Diagramma di funzionamento
Diagramma di funzionamento

Vediamo i passi nel dettaglio:

  1. Il cliente invia un messaggio tramite WhatsApp
  2. Appena Twilio riceve il messaggio, vengono invocate le API di Google Translate per identificare la lingua
  3. Se il messaggio è in lingua italiana, verrà inviato a Dialogflow per il riconoscimento dell'intento
  4. Se il messaggio è in lingua inglese, Twilio Autopilot riceverà il messaggio e ne identificherà l'intento
  5. Una volta identificato l'intento, elaboreremo la risposta sulla piattaforma Twilio e invieremo un messaggio di risposta all'utente (sempre tramite WhatsApp)

Attivazione di WhatsApp sull'account Twilio

Per ricevere gli ordini useremo WhatsApp. In particolare la sandbox di WhatsApp che può essere attivata su ogni account, anche trial. Se volete ottenere un vostro numero WhatsApp potete farlo seguendo questa procedura (tenete a mente che può richiedere tra le due e le quattro settimane).

Nella console di Twilio aprite questa pagina: dopo aver accettato di creare una sandbox, al centro della pagina trovate un numero di telefono e una frase (tipicamente "join xxx-xxxxx").

Attivazione Sandbox di WhatsApp

Usate WhatsApp sul vostro telefono per inviare la frase specificata al numero indicato. Una volta fatto dovreste vedere il seguente messaggio:

Sandbox di WhatsApp attivata

Contemporaneamente dovreste aver ricevuto un messaggio di risposta sul vostro Whatsapp. Ora tutto è pronto per scambiare messaggi con il vostro account WhatsApp tramite le API di Twilio.

Il passo successivo è quello di aggiungere una funzione per la gestione dei messaggi. Per fare questo, utilizzeremo le Twilio Functions:

  • Andate a questa pagina nella console di Twilio
  • Cliccate su "Create a Function"
  • Sul pop-up che si apre scegliete "Blank" e cliccate "Create"
  • Nella pagina successiva, scrivete "Inbound Message" come "Function Name"
  • Per il "PATH" scrivete "/inbound-message"
  • Nella parte del codice, copiate e incollate il seguente codice
function detectLanguage() {
    // Function for detecting language of incoming message
   return Promise.resolve('it')
}

exports.handler = function(context, event, callback) {
    detectLanguage()
    .then(language => {
        let response = ''
        switch (language) {
            case 'it':
            // Gestisci richieste in italiano
            response = 'Stiamo processando il vostro ordine'
            break;
            case 'en': 
            // Handle requests in english
            response = 'We are handling your order'
            break; 
        }
        callback(null, response);
    })
    .catch(error => {
        callback(error, null);
    })
};
  • Cliccate sul tasto "Save" e aspettate fino a che il messaggio " Your Function has successfully been deployed." viene visualizzato
  • Cliccate sul tasto per copiare il path della funzione (bottone accanto al path della funzione)

Dobbiamo ora collegare questa funzione all'evento di arrivo di nuovi messaggi su WhatsApp:

Impostazioni per il webhook di WhatsApp
  • Cliccate sul tasto "Save" per salvare la configurazione.

Provate ora ad inviare un messaggio qualsiasi tramite WhatsApp al numero di cui sopra. Se la configurazione è stata fatta correttamente, dovreste ricevere il seguente messaggio di risposta:

Messaggio di test per WhatsApp

Vediamo ora come sviluppare la funzione per identificare la lingua e di conseguenza scegliere il motore per il riconoscimento del linguaggio. Per prima cosa dobbiamo creare un nuovo progetto su Google Cloud

Creazione di un nuovo progetto su Google Cloud

In questo progetto useremo i seguenti servizi Google Cloud:

  • Dialogflow: in questo tutorial useremo la standard edition che è gratuita
  • Cloud Translation API: in questo tutorial useremo l'API per il riconoscimento della lingua. Questo servizio ha un costo, ma si possono usare metodi alternativi per ottenere lo stesso risultato, come un numero di telefono differente per ogni lingua.

Iniziamo creando un nuovo progetto chiamato "Barista". Per fare questo, visitate questa pagina e, dopo aver fatto la registrazione (o l'accesso se avete già un account), cliccate sul tasto "Create Project" e date il nome "Barista". Una volta fatto ciò, dovrete completare questi due passi:

  • Attivare le Translate API e generare una API key per l'accesso (opzionale)
  • Abilitare le Dialogflow API

Riconoscimento della lingua (opzionale)

Per prima cosa dobbiamo abilitare le Translate API sul progetto che abbiamo appena creato:

  • Andate a questa pagina ("API and Services")
  • Cliccate su "+ Enable API and Services"
  • Cercate "Cloud Translation API" e confermate
  • Cliccate "Enable" nella pagina che si apre

A questo punto, dobbiamo generare una API key che ci servirà per utilizzare le Translate API dal nostro backend:

  • Andate a questa pagina ("Credentials")
  • Cliccate su "+ Create Credentials"
  • Selezionate "API key"
  • Copiate l'API key e salvatela in un posto sicuro
  • Cliccate sul tasto "Close"

Aggiungiamo l'implementazione della funzione detectLanguage() nella funzione create precedentemente:

  • Aprite la pagina di configurazione delle Twilio Functions
  • Cliccate sul tasto "+" sotto "Environmental Variables"
  • Aggiungete una nuova variable ambiente chiamata "TRANSLATE_API_KEY" (tutto maiuscolo) e come valore incollate la API key che avete ottenuto sulla console di Google:

Variabili di ambiente per Twilio Runtime

  • Aggiungere il pacchetto node-fetch nella lista delle "Dependencies". Cliccate sul tasto "+" e nella casella vuota che appare scrivete "node-fetch"

Dipendenze aggiuntive per Twilio Runtime

  • Cliccate sul tasto "Save"
  • Aprite ora la funzione che avete creato precedentemente (ovvero Inbound Message) e sostituite col seguente codice:
const fetch = require('node-fetch');

function detectLanguage(text) {
    body = {
        q: text
    }

    return fetch('https://translation.googleapis.com/language/translate/v2/detect?key=' + process.env.TRANSLATE_API_KEY, {
        method: 'POST',
        body:    JSON.stringify(body),
        headers: { 'Content-Type': 'application/json' }, 
    })
    .then(response => response.json())
    .then(resJson => Promise.resolve(resJson.data.detections[0][0].language));
}

exports.handler = function(context, event, callback) {
    detectLanguage(event.Body)
    .then(language => {
        console.log(language)
        let response = ''
        switch (language) {
            case 'it':
            // Gestisci richieste in italiano
            response = 'Stiamo processando il vostro ordine'
            break;
            case 'en': 
            // Handle requests in english
            response = 'We are handling your order'
            break; 
        }
        callback(null, response);
    })
    .catch(error => {
        callback(error, null);
    })
};
  • A parte l'implementazione della funzione detectLanguage() abbiamo anche cambiato il modo in cui chiamiamo questa funzione in linea 18: abbiamo aggiunto una variabile, che nel nostro caso sarà il contenuto del messaggio ricevuto tramite WhatsApp (ovvero event.Body).
  • Salvate ora la funzione cliccando il tasto "Save" e attendete fino a che appare il messaggio che ne conferma il deployment.

Proviamo ora ad inviare tramite WhatsApp i seguenti messaggi: "Vorrei avere un cappuccino per favore" e "Can I have a cappuccino?". Se tutto funziona correttamente, dovreste ricevere le seguenti risposte:

Messaggi di test per il riconoscimento della lingua

Siamo ora pronti ad aggiungere i motori per il riconoscimento del linguaggio. Iniziamo con quello in inglese.

Aggiungere Twilio Autopilot

In questo paragrafo andremo ad aggiungere un chatbot in inglese utilizzando Twilio Autopilot, il motore di riconoscimento di linguaggio naturale (NLU) messo a disposizione da Twilio:

  • Aprite la pagina con lista di chatbot sulla console di Twilio
  • Cliccate sul tasto "Start from scratch" per crearne uno nuovo
  • Date come nome "Barista" e cliccate su "Create Bot"

Il chatbot viene creato già preconfigurato con alcuni task di base, come il messaggio di benvenuto ("greeting"). Aggiungiamo ora il task per la ricezione dell'ordine:

  • Aprite la pagina del chatbot che avete creato (Barista)
  • Cliccate sul tasto "Add Task"
  • Date il nome "order" al nuovo task e cliccate sul tasto "Add"
  • Cliccate ora sul link "Train" a destra del nuovo task creato

Schermata dei task di Autopilot

  • Per prima cosa aggiungiamo un nuovo campo variabile per il tipo di bevanda ordinata. Cliccate su "Fields" al centro della pagina:

Campi variabili per Autopilot

  • Compilate "Field Name" e "Field Type" come sotto. Per il "Field Type" una volta scritto "beverage", cliccate sulla voce "Create beverage" sul menu a tendina che appare:

Aggiungere il campo variabile "beverage"

  • Cliccate su "0 values" a destra del nuovo valore creato e aggiungete i seguenti valori: "latte", "cappuccino" e "coffee":

Aggiungere i valori per il campo variabile "beverage"

  • Tornate ora alla schermata con la lista dei valori variabili per il task "order" usando il tasto back del vostro browser
  • Aggiungete il valore "number" con tipo "Twilio.Number" (questa volta selezionate dal menu a tendina). Questo e' un valore predefinito che permette Autopilot di riconoscere numeri comunque siano scritti (per esempio riconosce sia "1" che "uno"):

Campo variabile numerico

  • Una volta aggiunti i campi variabili, dobbiamo istruire autopilot con delle frasi di esempio. Per fare questo cliccate su "Samples" e poi aggiungete la seguente frase: Can I have {number} {beverage}?. Come potete vedere in questa frase utilizziamo i due campi variabili definiti negli step precedenti, racchiudendoli tra parentesi graffe:

Frasi di training per Autopilot

  • Ora cliccate su "Add Sample" per aggiungere la frase
  • Cliccate su "Build Model" (in basso) per istruire il modello usando la frase di esempio appena aggiunta.

Una volta che il modello e' stato ricostruito, potete testare usando il simulatore accessibile sotto la voce "Simulator" nella barra di sinistra (o attraverso questa pagina se avete chiamato il vostro chatbot "Barista"). Il simulatore e' un ottimo strumento per verificare che il vostro chatbot funzioni bene prima di pubblicarlo. Se per esempio provate ad inserire la frase Can I have three latte? nella casella di testo che dice "Type a Message" e premete il tasto di invio, potete verificare nella finestra di destra (Debug View) che il task riconosciuto è order e che i due parametri {number} e {beverage} hanno valore 3 e latte:

Simulatore di Autopilot

Come vedete dallo screenshot sopra, la risposta del chatbot e' This is your new Task. Per cambiare la risposta torniamo alla lista dei task (cliccate su "Tasks" nel menu di sinistra) e questa volta clicchiamo su "Program". In questa schermata dobbiamo istruire autopilot a rispondere una volta ricevuto l'ordine. Per semplicità in questo esempio, utilizzeremo una frase predeterminata: in alternativa, utilizzando una Action URL invece dell'Action Bin, potete programmare autopilot per creare frasi dinamiche. Copiate e incollate il seguente JSON nella casella di testo:

{
	"actions": [
		{
			"say": "Thanks! We are preparing your order." 
		}
	]
}

Una volta fatto, cliccate su "Save" e "Build model". Andiamo ora a prendere la URL di questo chatbot: questo ci servirà a girare il messaggio ricevuto tramite Whatsapp:

  • Cliccate su "Channels" nel menu di sinistra (o cliccate qui se il vostro chatbot si chiama "Barista")
  • Cliccate sulla casella "WhatsApp"
  • Cliccate sul tasto copia accanto all'indirizzo

Canale WhatsApp per Autopilot

Modifichiamo ora la funzione Inbound Message per ridirigere il messaggio ricevuto al nostro chatbot usando la url copiata sopra:

  • Cliccate qui per andare sulla pagina delle Twilio Functions
  • Cliccate sulla funzione che abbiamo creato precedentemente (ovvero Inbound Message)
  • Modificate il corpo della funzione principale (ovvero export.handler) utilizzando il codice qui sotto e sostituendo <autopilot url> con la url che avete copiato sopra:
exports.handler = function(context, event, callback) {
    detectLanguage(event.Body)
    .then(language => {
        console.log(language)
        let response = ''
        switch (language) {
            case 'it':
            // Gestisci richieste in italiano
            response = 'Stiamo processando il vostro ordine'
            break;
            case 'en': 
            // Handle requests in english
            response = new Twilio.twiml.MessagingResponse();
            response.redirect('<autopilot url>')
            callback(null, response);
            break; 
        }
    })
    .catch(error => {
        callback(error, null);
    })
};

Questo codice utilizza la libreria di Twilio per creare un nuovo TwiML: potete trovare maggiori informazioni sui TwiML a questa pagina.

Dopo aver salvato la funzione, provate di nuovo ad inviare il seguente messaggio tramite il vostro WhatsApp: "Can I have a latte?". Questa volta dovreste ricevere come risposta il messaggio che abbiamo impostato sopra per il task order, ovvero Thanks! We are preparing your order.

Questo e' solo un esempio molto semplice di cosa può fare Autopilot. Vi consiglio di leggere una delle guide che potete trovare qui per scoprire tutte le potenzialità di Twilio Autopilot.

Vediamo ora come utilizzare Google Dialogflow per gestire gli ordini in italiano.

Creare un nuovo Agente in Dialogflow

In questo capitolo vedremo come creare un agente in Dialogflow che interpreta richieste in italiano. Per creare un nuovo agente in Dialogflow:

  • Accedete alla console di Dialogflow
  • Cliccate sulla freccia in basso nel menu di sinistra e scegliere "New Agent"

Aggiungere un nuovo agente su Dialogflow

  • Date il nome "Barista" al nuovo agente
  • Selezionate come lingua principale l'Italiano

Scelta del linguaggio per Dialogflow

  • Cliccate sul tasto "Create"

Ecco come programmare il nostro chatbot:

  • Cliccate sulla "+" a destra di "Intents" nel menu di sinistra
  • Come nome dell'intento scrivete "ordine"
  • Cliccate su "Add training phrases"
  • Come "Training Phrases" scrivete la seguente: Vorrei due cappuccini per favore
  • Fate doppio click sulla parole due: verrà mostrato un menu dal quale selezionerete @sys.number. Questo indica a Dialogflow che ci aspettiamo un numero a questo punto della frase.

Tipi numerici in Dialogflow

  • Fate doppio click sulla parola capuccini e dal menu scegliete @sys.any. In questo caso stiamo impostando Dialogflow per estrarre la parola che segue il numero.
  • Ripetete la stessa procedura per aggiungere la frase Potrei avere un caffè? (specificando rispettivamente @sys.number e @sys.any per "un" e "caffè")
  • Opzionale: aggiungete altre frasi per aumentare le probabilità che l'intento sia riconosciuto
  • Cliccate su "Save" per salvare il nuovo intento
  • Attendere fino a che "Agent training completed" viene visualizzato in basso a destra della pagina

Per questo esempio non aggiungiamo nessuna risposta all'intento, perché gestiremo la risposta direttamente nella funzione che abbiamo creato sopra. Prima di fare questo, dobbiamo creare un nuovo utente su Dialogflow che utilizzeremo per fare richieste da Twilio:

  • Cliccate sulla rotellina accanto al nome dell'agente nella barra sinistra per mostrare le proprietà del progetto.

Schermata del progetto in Dialogflow

  • Nelle informazione relative al "Service Account" dovreste avere un lungo indirizzo di email. Se non lo avete cliccate sul'icona per fare il refresh del service account.
  • Cliccate sull'indirizzo email del "Service account"
  • Questo aprirà la pagina di Google Cloud Console relativa ai "Service account"
  • Cliccate sul service account che era visualizzato nella pagine di Dialogflow
  • Cliccate "Edit" e poi "+ Create Key"
  • Nel menu laterale che si apre, scegliete JSON e cliccate su "Create"
  • Il vostro browser scaricherà automaticamente sul file json. Tenetelo da parte perché questo ci servirà più avanti per l'autenticazione
  • Cliccate su "Done" e poi su Save

Siamo ora pronti a collegare l'agente Dialogflow a Twilio

Collegare l'Agente di Dialogflow con Twilio

In questo paragrafo vedremo come utilizzare le Google API per inviare il messaggio a Dialogflow e gestire poi l'intento riconosciuto attraverso Twilio. Per prima cosa aggiungiamo il client di google all'ambiente Runtime di Twilio:

Aggiungere la libreria di Dialogflow

Ora aggiungiamo il codice per gestire la chiamata a Dialogflow. Per prima cosa aprite la funzione Inbound Message dalla pagina con la lista delle funzioni. Una volta aperta ci sono tre aggiunte da fare.

Configurazione di Dialogflow

La prima cosa da aggiungere (a parte la libreria Node.JS di Dialogflow) sono le informazioni di autenticazione di Dialogflow che abbiamo scaricato nel file JSON precedentemente. Aggiungete quindi il seguente codice in cima alla funzione:

const fetch = require('node-fetch');
const dialogflow = require('dialogflow');

const projectId = '<Dialogflow JSON project_id>';
const dfConfig = {
    credentials: {
        private_key: '<Dialogflow JSON private_key>',
        client_email: '<Dialogflow JSON client_email>'
    }
}

Tutte le informazioni necessarie sono incluse nel file JSON che avete scaricato da Dialogflow quando avete creato il service account.

Connettore a Dialogflow

Questa funzione realizza la chiamata a Dialogflow per il riconoscimento degli intenti:

async function detectIntent(query) {
  // New session client
  const sessionClient = new dialogflow.SessionsClient(dfConfig);
  // The path to identify the agent that owns the created intent.
  const sessionPath = sessionClient.sessionPath(projectId, '123456');

  // The text query request.
  const request = {
    session: sessionPath,
    queryInput: {
      text: {
        text: query,
        languageCode: 'it',
      },
    },
  };

  const responses = await sessionClient.detectIntent(request);
  return responses[0];
}

Nella chiamata sessionClient.sessionPath() il secondo argomento dovrebbe essere una stringa che identifica univocamente l'interazione. Per semplicità abbiamo aggiunto una stringa statica, ma in produzione sarebbe meglio aggiungerci un identificativo unico. Dialogflow consente l'aggiunta di un contesto all'operazione di riconoscimento dell'intento (maggiori informazioni qui). Questo consente di creare una conversazione in cui il chatbot risponde in funzione delle precedenti domande / risposte. Per semplicità non abbiamo incluso il contesto nel nostro esempio, ma potete farlo aggiungendolo all'oggetto request.

Funzione di trasferimento a Dialogflow

La funzione detectIntent() definita sopra, viene chiamata da una nuova funzione dove possiamo aggiungere (per esempio) tutte le informazioni di debug:

async function executeQuery(query) {
    let queryResult = {}
    try {
      console.log(`Sending Query: ${query}`);
      intentResponse = await detectIntent(query);
      console.log('Detected intent');
      console.log(
        `Intent Name: ${intentResponse.queryResult.intent.displayName}`
      );
      console 
      // Use the context from this response for next queries
      queryResult.success = true
      queryResult.intent = intentResponse.queryResult.intent.displayName
      queryResult.parameters = intentResponse.queryResult.parameters;
    } catch (error) {
      console.log('executeQuery() error')
      console.log(error);
      queryResult.success = false
    }
    return queryResult
}

Questa funzione manipolerà il risultato di Dialogflow per renderlo più gestibile all'interno della funzione principale per la gestione del messaggio in arrivo. Nell'esempio abbiamo creato un nuovo oggetto chiamato queryResult che include alcune (ma non tutte) le informazioni restituite da Dialogflow. Per aggiungerne altre (come il contesto) potete consultare la documentazione di Dialogflow.

Aggiungiamo ora la chiamata a questa funzione all'interno dell'handler per i messaggi in arrivo:

exports.handler = function(context, event, callback) {
    detectLanguage(event.Body)
    .then(language => {
        let response = ''
        switch (language) {
            case 'it':
                // Gestisci richieste in italiano
                executeQuery(event.Body)
                .then(result => {
                    if (result.intent === 'ordine') {
                        response = `Grazie! Stiamo preparando ${result.parameters.fields.number.numberValue} ${result.parameters.fields.any.stringValue}`; 
                    } else {
                        response = 'Non ho capito. Puoi provare di nuovo?';
                    }
                    callback(null, response);
                })
                break;
            case 'en': 
                // Handle requests in english
// ...

Come potete vedere dal codice precedente, in questo esempio gestiamo solo l'intento "ordine". Potete aggiungere qui la gestione di altri intenti (come ad esempio il messaggio di benvenuto, ovvero l'intento "Default Welcome Intent").

Inoltre notate come nella risposta utilizziamo i parametri riconosciuti da Dialogflow, ovvero result.parameters.fields.number.numberValue per il numero, e result.parameters.fields.any.stringValue per il tipo di bevanda. Potete utilizzare queste informazioni per aggiungere l'ordine al vostro sistema di gestione ordini.

Potete trovate qui il codice finale della funzione da copiare e incollare nella vostra funzione.

Salvate ora la funzione, e tenete aperta questa pagina. La utilizzeremo nel prossimo paragrafo per controllare i log e verificare che tutto funzioni bene.

Testare la soluzione

Per prima cosa proveremo con una richiesta in italiano. Andate sul vostro WhatsApp e inviate il seguente messaggio: Potrei avere due caffè?. Per prima cosa vediamo cosa viene visualizzato nei log della funzione:

Log di esecuzione della funzione

Come potete vedere l'intento e' stato riconosciuto correttamente. Se ora guardate su WhatsApp dovreste aver ricevuto la risposta seguente:

Test per messaggi in italiano

Notate che la risposta contiene il numero "2" (e non la parola "due" come nella frase originaria). Questo perché per creare la risposta stiamo utilizzando il parametro result.parameters.fields.number.numberValue che è un parametro numerico (e si presta meglio ad essere utilizzato come input al vostro sistema di gestione degli ordini).

Proviamo ora con la richiesta in inglese. Inviate: I'll have one espresso. In questo caso, la frase utilizzata è differente da quella utilizzata per il training di Autopilot. Nonostante questo, Twilio Autopilot riconosce l'intento correttamente, e risponde di conseguenza:

Test per messaggi in inglese

Nella risposta in inglese non abbiamo riproposto l'ordine, ma questo è possibile creando una azione dinamica utilizzando una "Action URL" all'interno di Autopilot (per maggiori informazioni potete riferirvi a questa documentazione)

Congratulazioni!

Ora tutto è pronto per pubblicare la vostra soluzione e ricevere gli ordini. In questo repository su GitHub potete trovare alcuni script utili per configurare automaticamente il vostro progetto con il codice illustrato in questo articolo, usando Twilio Runtime.

Tra le altre cose che potete aggiungere:

  • Risposte dinamiche tramite Twilio Autopilot
  • Gestione dei messaggi di benvenuto e dei messaggi in caso di errore
  • Aggiunta di caratteristiche specifiche all'ordine (per esempio per gestire ordini del tipo "cappuccino tiepido in tazza grande")

Le opzioni sono infinite, e non vediamo l'ora di vedere cos'altro creerete con Twilio!

Giuseppe Verni è Solution Engineer per Twilio e aiuta clienti in Italia e in EMEA a sviluppare le loro soluzioni basate sui prodotti Twilio. Per scrivere questo articolo Giuseppe ha consumato 13 espresso e 2 cappuccini, e nessuna macchinetta del caffè è stata maltrattata durante la stesura. Giuseppe può essere raggiunto a gverni [at] twilio.com o su GitHub a https://github.com/vernig