Microsoft Bot Framework: Dialog Bot Funzionamento e analisi del codice sorgente di Dialog Bot, realizzato con il Microsoft Bot Framework SDK per Visual Studio 2019

Microsoft Bot Framework: Dialog Bot

Questo articolo fa parte di una serie di approfondimenti sul Microsoft Bot Framework, il sistema messo a disposizione da Microsoft per sviluppare bot di vario tipo, ed è dedicato all'analisi del codice sorgente di un bot realizzato con il Microsoft Bot Framework SDK per Visual Studio 2019. Stavolta vedremo all’opera un bot leggermente più sofisticato dell'EchoBot che abbiamo illustrato in precedenza: in questo caso le attività del bot non si limiteranno a ripetere il testo descritto dall’utente ma consentiranno di attivare una vera e propria procedura di registrazione interattiva, gestita attraverso un classico meccanismo di domanda e risposta.

DialogBot all'opera

Il modo migliore per comprendere il funzionamento del nuovo bot - che abbiamo chiamato DialogBot - è quello di vederlo all'opera.

Come si può vedere osservando le interazioni mostrate all'interno del video, la sessione di questions & answers può essere iniziata dall’utente in qualsiasi momento utilizzando una parola chiave, un cosiddetto TRIGGER: nel nostro caso il trigger è costituito dalla parola REGISTRAMI, ricevuta la quale il bot dà inizio al processo di registrazione.

Uno sguardo al codice

Dopo averlo visto all’opera, diamo ora uno sguardo al codice sorgente del DialogBot. Per ragioni di spazio eviteremo di ribadire le istruzioni necessarie per installare l'ambiente di sviluppo (che abbiamo già spiegate qui): daremo quindi per scontato che il lettore abbia già il framework di lavoro correttamente impostato e pronto per analizzare il codice sorgente di DialogBot, disponibile su GitHub a questo indirizzo.

Cominciamo ad analizzare il file BotController.cs, che corrisponde all'implementazione del controller principale del progetto:

Come possiamo vedere, il Controller è assolutamente identico a quello del precedente EchoBot: anche in questo caso l’endpoint HTTP ha unicamente il compito di ricevere le richieste e passarle a un adapter che gestirà le interazioni con il bot.

Le differenze più interessanti riguardano ovviamente la classe relativa al Bot vero e proprio (DialogBot.cs), che presenta numerose differenze. Cominciamo dal costruttore, che come possiamo vedere presenta alcuni nuovi oggetti che vale la pena approfondire:

I primi tre sono degli state management object che consentono di gestire tre diversi ambiti:

  • lo stato della conversation all'interno della quale un utente rivolge un messaggio al bot, a prescindere dall’user;
  • lo stato dell'utente che rivolge un messaggio al bot, a prescindere dalla conversation;
  • lo stato dell'utente che rivolge un messaggio al bot all’interno di una conversation ben precisa.

Questi oggetti vengono istanziati tramite Dependency Injection e consentono di gestire, in modo molto semplice, le informazioni chiave della chat e dell’utente che sta interagendo con il bot. Come possiamo vedere analizzando il primo dei tre handler presenti all’interno della classe del bot, questi oggetti vengono aggiornati ad ogni turn.

L’ultimo oggetto che viene passato come parametro del costruttore è di tipo Dialog, la classe base utilizzata dal Bot Framework per rappresentare le finestre di dialogo: le Dialog sono un concetto centrale di questa SDK, in quanto consentono di gestire una conversazione di lunga durata con l'utente. Se negli articoli precedenti avevamo descritto i turni come un singolo scambio di una partita di tennis tra l’utente e il bot, possiamo pensare alle Dialog come a una serie di scambi che termina con l’assegnazione di un singolo punto. Si tratta dunque di una sorta di raggruppamento di turni collegati tra loro, molto utile quando si ha necessità di gestire interazioni di breve o lunga durata tra l’utente e il bot.

La Dialog è il componente principale attorno a cui ruota il nostro DialogBot: come possiamo vedere dal codice, il secondo dei tre handler methods - quello che gestisce l’arrivo dei messaggi degli utenti, che costituiva il cuore del funzionamento dell’EchoBot nonché di quasi ogni Bot - esegue proprio l’oggetto Dialog, delegando a lui la gestione di tutte le eventuali operazioni da effettuare:

Come possiamo vedere osservando la condizione IF presente al centro del metodo, la Dialog viene chiamata in causa solo se si verifica una di due possibili condizioni: se l’utente che si rivolge al bot ha già iniziato il processo di registrazione, quindi il messaggio è una risposta a una delle domande del bot, oppure se ha scritto la parola chiave, che nel codice è chiamata TriggerText, ovvero la stringa di testo che dà inizio al questionario stesso: nel nostro caso, il TriggerText è “REGISTRAMI”.

Se nessuna di queste due condizioni è TRUE, il Bot non fa nulla: evidentemente l’utente che si è rivolto al bot non ha ancora iniziato la procedura di registrazione, né ha dichiarato di volerlo fare in quanto non ha digitato il TriggerText.

Se invece almeno una delle due condizioni è TRUE, la dialog viene lanciata: quando si lancia una Dialog, l’SDK richiede di fornire un accessor che sarà utilizzato per memorizzare lo stato della dialog; questo accessor può puntare a uno dei tre state management object che abbiamo visto poco fa: è molto importante scegliere quello giusto, a seconda di ciò che vogliamo fare. Cerchiamo di capire bene questo concetto fondamentale analizzando le tre possibilità che abbiamo a disposizione:

  • OPZIONE 1: memorizziamo lo stato della Dialog nel ConversationState: poiché il ConversationState è relativo alla chat, questo significa che avremo una singola dialog comune a tutti gli utenti. E’ una possibilità che potrebbe funzionare per un quiz nel quale viene premiato il primo utente che risponde, ma di certo è un approccio inadeguato per una procedura di registrazione, che prevede che ciascun utente risponda a tutte le domande in modo autonomo e indipendente dagli altri.
  • OPZIONE 2: memorizziamo lo stato della Dialog nell’UserState, ovvero a livello di utente: questo significa che avremo una dialog per ciascun utente, indipendentemente dalla chat. Questa è l’ipotesi più compatibile con le nostre esigenze, in quanto consentirà al bot di fornire ad ogni utente un percorso autonomo e indipendente. Non a caso, è l’opzione che abbiamo scelto di implementare.
  • OPZIONE 3: memorizziamo lo stato della Dialog nel PrivateConversationState, ovvero a livello di utente e di chat; questo significa che potremmo avere più di una dialog per ciascun utente, nel caso in cui l’utente utilizzasse la TriggerText in due chat diverse: ad esempio, all’interno di una chat di gruppo e contemporaneamente in una chat privata con il bot. Si tratta ovviamente di un’ipotesi indesiderabile per una procedura di registrazione che prevede la compilazione di un singolo questionario per ciascun utente, motivo per cui l’abbiamo scartata.

Il terzo metodo, anch’esso presente nell’EchoBot, gestisce le activities relative all’ingresso dei nuovi utenti: anche in questo caso il bot presenta un messaggio di benvenuto. A differenza di quanto accadeva con l’EchoBot, però, il welcome message comunica anche un’informazione in più, ovvero il TriggerText per dare inizio alla procedura di registrazione, che in questo modo viene reso noto ad ogni utente che fa il suo ingresso nella chat.

L'oggetto UserProfileDialog

Passiamo dunque in rassegna l’oggetto più importante di questo Bot, ovvero la Dialog: nello specifico si tratta di una UserProfileDialog, ovvero una classe personalizzata, derivata da ComponentDialog, che contiene i metodi necessari per gestire il workflow. La classe ComponentDialog è in buona sostanza una Dialog che consente di gestire una pluralità di sotto-Dialog che vengono create al suo interno: nel procedimento di registrazione che abbiamo visto poco fa svolgere dal bot, la classe UserProfileDialog è il contenitore delle singole domande che il bot poneva all’utente, ciascuna delle quali viene gestita da un metodo che a sua volta presenta all’utente una delle sotto-Dialog presenti nel contenitore.

Gran parte del funzionamento della classe UserProfileDialog si evince dal costruttore, il cui codice è riportato di seguito:

Come possiamo vedere, il costruttore svolge tre task principali:

  • La definizione di una proprietà di tipo UserProfile all’interno dello state management object relativo all’utente, lo stesso che viene aggiornato dalla classe del bot e che infatti proviene da lì, passato come parametro quando viene eseguita la Dialog. La classe UserProfile è un semplice contenitore tipizzato di informazioni relative all’utente.
  • I metodi che gestiranno ciascuna delle domande del questionario di registrazione, uno per domanda, organizzati all’interno di un array di WaterfallStep. Un WaterfallStep, come si evince dal nome, è un singolo passaggio di un processo waterfall, ovvero “a cascata”, cioè che prevede più passaggi consecutivi e sequenziali tra loro: per semplicità, possiamo immaginarci il processo di registrazione come una sorta di Wizard, di cui ciascuno di questi metodi costituisce un singolo passaggio.
  • In ultimo, il costruttore aggiunge le sotto-Dialog che saranno presentate agli utenti dai singoli metodi che compongono i vari step: come possiamo vedere c’è il TextPrompt, ovvero la sotto-Dialog che chiede all’utente di digitare un testo a piacere, utilizzata dallo step che chiede il nome, ma anche da quello che chiede la città; c’è il ChoicePrompt, ovvero la sotto-Dialog che consente l’utente di scegliere tra varie opzioni, utilizzata sia per le domande di tipo SI/NO che per la scelta del linguaggio di programmazione preferito; c’è il NumberPrompt, una sorta di TextPrompt che consente l’inserimento soltanto di valori numerici; e prima di tutte queste c’è la WaterfallDialog, che viene anche indicata come dialog di partenza nell’apposita proprietà InitialDialogId e alla quale è passato l’array di WaterfallSteps che contiene i metodi relativi ai singoli passaggi del questionario: il ruolo di questa Dialog è molto importante, perché fornisce lo stepContext a ciascun metodo, fondamentale per tenere traccia del percorso dell’utente tra uno step e l’altro: se la UserProfileDialog rappresenta il nostro Wizard, possiamo pensare alla WaterfallDialog come l’oggetto che consente di tenere traccia del percorso di navigazione dell’utente che si trova all’interno del Wizard.

I metodi relativi ai vari Step

Ora che abbiamo analizzato il costruttore, sappiamo come interpretare la restante parte del codice, ovvero i singoli metodi che consentono di gestire i vari step del processo di registrazione: o, come ormai sappiamo di poterli chiamare, i singoli WaterfallStep.

Ciascun WaterfallStep, grazie alla presenza dello stepContext fornito dalla WaterfallDialog, è in grado di recuperare le informazioni relative all’utente corrente, comprese le risposte da lui fornite nel corso degli step precedenti, nonché di generare una o più MessageActivities per poter comunicare con lui. L’unico obbligo che ciascun WaterfallStep deve rispettare è il valore di ritorno: ciascuno step deve restituire obbligatoriamente o una delle sotto-dialog previste o la fine della Waterfall.

Questa seconda possibilità è solitamente prerogativa dell’ultimo step, l’unico che non chiede all’utente ulteriori informazioni ma si limita a svolgere i saluti del caso, oltre a gestire le eventuali attività di back-end connesse alla finalizzazione della procedura: nel caso del nostro DialogBot il metodo in questione è il SummaryStepAsync, che come possiamo vedere si congeda dall’utente e provvede a memorizzare le sue informazioni nel caso in cui la registrazione sia stata confermata, nonché a inviare una e-mail di riepilogo:

Nel caso specifico la procedura di memorizzazione che quella di invio e-mail non sono ancora state implementate: al loro posto, per il momento, c’è un semplice to-do, sufficiente comunque a darci l’idea di come potrebbe funzionare la registrazione in caso di utilizzo reale del bot.

L'oggetto UserProfile

Prima di abbandonare il codice del nostro DialogBot diamo un’occhiata alla classe UserProfile, ovvero quella che il bot utilizza per memorizzare le risposte ricevute dall’utente.

Come possiamo vedere  si tratta di una classe estremamente semplice, che ricorda una Entity di un progetto basato su EntityFramework Core: del resto, in un ipotetico scenario di produzione che preveda il salvataggio delle risposte ricevute dagli utenti su un database, sarebbe del tutto possibile sostituirla con una Entity vera e propria. L’unica proprietà che merita due parole è IsRegistering, che viene impostata a TRUE all’inizio del primo WaterfallStep e poi nuovamente a FALSE alla fine dell’ultimo: quella è la proprietà che consente al bot di comprendere se l’autore del messaggio che viene di volta in volta intercettato ha iniziato la procedura di registrazione (e quindi il suo messaggio va gestito dalla UserProfileDialog) oppure no (e quindi il suo messaggio va ignorato): si tratta della condizione IF che abbiamo incontrato all’inizio della nostra panoramica, quando abbiamo analizzato la classe DialogBot.

Conclusioni

Per il momento è tutto. Nel prossimo articolo vedremo come mettere in esercizio il nostro Bot attraverso una serie di attività, tra cui: il Deploy del Bot su MS Azure, requisito fondamentale per poter poi collegare il bot a uno o più canali; l'attivazione del Bot su uno o più canali, tra cui Telegram, Slack e MS Teams; l'installazione del Bot sul canale, ovvero i passaggi richiesti dalla app di messaggistica per poter utilizzare il bot.

Articoli correlati

  1. Indice degli argomenti
  2. Introduzione: cos'è un bot?
  3. Microsoft Bot Framework: concetti generali
  4. Microsoft Bot Framework: Esempio semplice - Echo Bot
  5. Microsoft Bot Framework: Esempio complesso - Dialog Bot
  6. Microsoft Bot Framework: Deploy su Azure Bot Service
  7. Microsoft Bot Framework: Integrazione con MS Teams
Vuoi saperne di più sullo sviluppo di bot personalizzati per MS Teams, Facebook Messenger, Telegram, LINE o altre app di messaggistica in tempo reale? Scrivici per comunicarci le tue esigenze o richiedi un preventivo gratuito e senza impegno per realizzare il tuo progetto!

Fork me on GitHub

About Ryan

IT Project Manager, Web Interface Architect e Lead Developer di numerosi siti e servizi web ad alto traffico in Italia e in Europa. Dal 2010 si occupa anche della progettazione di App e giochi per dispositivi Android, iOS e Mobile Phone per conto di numerose società italiane. Microsoft MVP for Development Technologies dal 2018.

View all posts by Ryan

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *


The reCAPTCHA verification period has expired. Please reload the page.

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.