Pour cette nouvelle année, je me suis amusé à intégrer une action custom Google Home avec une fonction Azure en backend. Mon but était simplement de pouvoir demander à Google ce que l’on pouvait avoir à la TV ce soir.

L’idée m’est venu simplement parce que cette fonctionnalité n’existait pas sur Google Home, et que cela m’a fait un bon cas d’usage de développement custom sur une Google Home que j’ai maintenant depuis 2 ans sans l’avoir utilisé autrement que pour me donner l’heure ou chercher dans Wiki …
Pour faire cela j’a maximisé l’utilisation de ressources peu onéreuses et server-less pour m’éviter des frais d’hébergement et de stockage des données vu qu’il s’agit ici de ma souscription personnelle.
Le vocabulaire des chat bots
Ceux qui ont déjà créés des chatbots peuvent sauter cet article qui ne leur apprendra certainement pas grand-chose, mais je tenais ici à reprendre les éléments essentiels à comprendre lorsque l’on démarre un projet de chatbot comme l’est le « Guide Télé » pour Google Home que j’ai créé.
Je ne vais pas aller plus loins dans les explications de comment fonctionne un Chat Bot, en effet vous trouverez une multitude de sites et blogs expliquant très bien les détails de ce genre de solutions.
Utterance
L’utterance, ou l’énoncé, est la phrase émise par l’utilisateur.
Intents
Les intents (intentions en français), sont comme le nom l’indique ce que l’utilisateur demande au chatbot. Il s’agit donc de l’action que notre chatbot doit être capable de réaliser.
Entities
Les entités sont les éléments clefs de la phrase qui sont utilisés par l’intent pour réaliser l’action conformément à ce que l’utilisateur souhaite. En d’autres termes cela peut être vu comme des paramètres d’exécution de l’action.
Tous ces éléments sont essentiels dans la définition de votre Chat bot puisqu’ils sont utilisés par le moteur NLP (Natural Language Processing) qu’est DialogFlow pour pouvoir exécuter la bonne action au regard de ce que l’utilisateur dit.
Google Dialog Flow et Google Actions
Google Actions et Google Dialog Flow sont les plateformes Google utilisée pour mettre en œuvre les chatbots (ou agent conversationnels) consommés par l’assistant google de nos Google Home. Nous allons ici démarrer simplement depuis ces outils en ligne pour créer notre agent.
Créer un projet depuis la plateforme : Actions on Google (vous allez voir, c’est assez simple).

Le Wizard de google nous demande ensuite quel type de projet nous souhaitons démarrer, dans mon cas, j’ai choisi un projet Smart Home :

Votre projet a été créé sur la plateforme, SUPER ! maintenant commençons à définir les différentes intentions que nous souhaitons réaliser.
Mes premières Intentions
Une fois notre projet créé, nous allons pouvoir créer les intentions que nous souhaitons réaliser pour nos utilisateurs.
Dans mon cas, l’application est là pour me donner des informations sur la programmation télévisuel. J’ai donc commencé par les intentions suivantes :
LookupIntent: Simple consultation de la programmation, elle sera en charge de répondre par exemple aux questions suivantes :
Qu'est-ce qu'il y a ce soir sur TF1 ?
Qu'est-ce qu'on regarde ?
Qu'est-ce qu'il y a à partir de 22H ?
SearchCategoryIntent: Recherche des programmes de la catégorie souhaitée, elle pourra répondre par exemple aux questions suivantes :
Est-ce qu'il y a un documentaire ?
Est-ce qu'il y a une série ce soir ?
Sur quel chaine il y a un documentaire demain à 16H ?
Les entités
Dans les différentes intentions que j’ai mentionnées, vous aurez repéré quelques entités qu’il sera bon de pouvoir traiter afin de répondre au mieux à notre interlocuteur comme par exemple :
- La chaine demandée,
- La catégorie de programme recherchée
- La date recherchée,
- …
Commençons tout de suite donc à préparer nos entités qui seront utilisées dans la reconnaissance de nos intentions…
Création des entités
La création des entités dans Google Dialog se fait au travers de l’onglet « Develop », dans l’espace « Types ».
- Cliquer sur Develop
- Dérouler les Types
- Cliquer sur le bouton + pour ajouter un nouveau Type

Lorsque vous créerez votre type vous serez invité à lui fournir un certain nombre de valeurs possibles qui permettront à Google Dialog Flow de les reconnaitre dans les uterrances de l’utilisateur. Vous pouvez alors dans la partie « Values » fournir des synonymes représentant la même valeur de l’entité à laquelle vous souhaitez raccrocher, comme par exemple :

Une fois les différents types créés, nous pouvons démarrer la définition des intentions.
Création des intentions
La création des intentions dans Google Dialog se fait au travers de l’onglet « Develop », dans l’espace « Intents ».
- Cliquer sur Develop
- Dérouler les Intents
- Cliquer sur le bouton + pour ajouter une nouvelle intention
Vous serez alors invités à lui ajouter les différentes phrases à laquelle cette intention doit correspondre. Cela est très important, puisque ce sont les phrases qui seront utilisées lors de la phase d’entrainement qui sera initiée par Google Training dès que vous sauvegardez votre application.

Vous remarquerez qui surligne de différentes couleurs vos phrases lorsque qu’il repère dans celles-ci les entités que nous avons préalablement définies. Vous pourrez les changer supprimer et/ou ajouter si toutefois il ne le fait pas correctement ou si par exemple vous rajoutez des entités après avoir créé vos phrases d’entraînement.
Ces entités seront envoyées au backend sous la forme d’intent parameters et les types sont définis en dessous :

Dans mon cas, vous remarquerez que j’ai défini deux types de paramètres pour la définition de la date et l’heure à laquelle je souhaite rechercher. Le paramètre dateTime est un paramètre de type actions.type.DateTime (type par défaut) qui me permet de m’appuyer sur Google Assitant pour extraire une date à partir d’une phrase comme « demain à 18H« . En revanche, il m’a fallut un type particulier pour extraire une information plus spécifique à mon contexte comme par exemple « ce soir« , ou « ce soir en deuxième partie » c’est donc l’objet du paramètre datetimeToken. Mon intention sera alors en charge de comprendre ces deux paramètres et de répondre en conséquence.

Scène principale
Un concept que j’ai découvert avec Google Actions est le concept de Scène dans laquelle nous allons pouvoir faire entrer un utilisateur et à partir duquel il aura accès à certaines intentions et comportements de la part de l’assistant. Nous pouvons ainsi créer différentes scènes et faire transiter l’utilisateur de scène en scène au grès des intentions qu’il exprime. Il est alors possible de stocker dans une session de conversation, différents paramètres de notre conversation pour rendre les réponses contextualisées avec ce que l’utilisateur recherche.
Cela permet ainsi de créer des agents de conversations assez pointus et très proches de conversations que nous pourrions avoir avec un être humain..
Dans mon cas, j’ai utilisé une seule scène au moment où j’écris ce billet.

Slot filling
Un élément que je n’ai pas encore exploité dans ce chat bot est la capacité d’aller lui demander un détail sur une liste d’éléments que je présente à l’utilisateur. En effet, ici je présente la liste des programmes en fonction des paramètres de l’intention et je prévois de laisser l’utilisateur la possibilité de par exemple demander un détail sur un programme, la description, le public visé, … Cela se passe avec le slot filling où nous pouvons passer un élément de la liste dans un Intent Parameter … que j’expliquerai dans un article ultérieur.
Azure Function App Webhook
Voila, nous avons maintenant presque préparé l’intégralité de ce que devra faire l’agent conversationnel. Il nous faut maintenant travailler sur le backend qui sera appelé lorsque les intentions seront reconnues par l’assistant et lui répondre les informations à retourner.
Pour ce faire, Google Dialog Flow nous propose de réaliser un web hook (Webhooks | Conversational Actions | Google Developers) :
Quoi de mieu qu’une Function as a Service pour réaliser un Webhook, je vous le demande ? Ne connaissant pas trop les technologies Google (Cloud Functions) et maitrisant les technologies Azure, je préfère m’orienter sur une Azure Function pour l’occasion (ca marche aussi avec une Logic App, mais nous le verrons il nous faudra ingérer un fichier XML contenant la base de données des programmations, ce qui est difficilement faisable avec une LogicApp Seule).
Functions
J’ai défini dans mon application, 2 fonctions aux rôles distincts :
DBIntegration
Cette fonction est en charge de l’intégration de la base de données des chaines et programmes. Cette fonction sera déclenchée sur un TimerTrigger (planification).
public static async Task<XElement> LoadDatabase(ILogger log)
{
string filePath = "https://xmltv.ch/xmltv/xmltv-tnt.xml";
using (var handler = new HttpClientHandler())
{
using (var client = new HttpClient(handler))
{
log.LogInformation($"Opening stream at: {filePath}");
using (var fileStream = await client.GetStreamAsync(filePath))
{
log.LogDebug("Reading Stream...");
XElement result = await XElement.LoadAsync(fileStream, LoadOptions.None, CancellationToken.None);
log.LogDebug($"XML TV file parsed");
return result;
}
}
}
}
L’action est assez simple, je me base sur un fichier XML disponible sur l’URL https://xmltv.ch/xmltv/xmltv-tnt.xml contenant des informations sur les chaines ainsi que sur la programmation télévisuelle pour les 7 jours à venir. Le fichier XML est parsé via Linq To XML (pour des raisons de rapidité de développement) et les données sont poussées dans des tables Azure Storage par batch TV-Guide-Webhook/DBIntegration.cs at master · kbeaugrand/TV-Guide-Webhook (github.com).
GoogleDialog
Viens maintenant la partie du web hook que nous devons réaliser pour répondre à l’assistant google.
Une seule fonction, plusieurs intents :
[FunctionName("GoogleDialog")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
using (var reader = new StreamReader(req.Body))
{
var data = await reader.ReadToEndAsync();
var request = JsonConvert.DeserializeObject<GoogleDialogFlowRequest>(data);
IntentBase intent = null;
switch (request.Intent.Name)
{
case "actions.intent.MAIN":
intent = new WelcomeIntent();
break;
case "LookUpIntent":
intent = new LookupIntent();
break;
case "SearchCategoryIntent":
intent = new SearchCategoryIntent();
break;
default:
return new BadRequestObjectResult($"Intent {request.Intent.Name} is not handled!");
}
return await intent.Handle(request, log);
}
}
Azure Cloud Table Storage
La partie que j’aimerais vous détailler ici, c’est la communication avec le Azure Cloud Table et les requêtes. L’idée est que c’est effectivement très simple et « habituel » de se connecter à un SGBD standard ou même à un CosmosDB dans le cadre d’une Azure Function pour stocker et requêter des données, mais Azure propose également l’utilisation de Azure Storage Account pour y stocker des Fichiers (couramment utilisé), des Tables, des Queues, c’est en fait un point d’intégration assez intéressant et peu couteux surtout pour des workloads aussi simple que celui que je suis en train de réaliser.
La connexion avec votre Azure Storage Table est relativement simple, pour cela vous aurez besoin de votre connection string (qui est la même pour la connexion à votre Storage Account Blob), ainsi que du nom de la table à requêter:
CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(Constants.StorageAccountConnectionString);
CloudTableClient tableClient = cloudStorageAccount.CreateCloudTableClient();
cloudTable = tableClient.GetTableReference(tableName);
Ensuite, la recherche et l’obtention du résultat de la recherche à partir de cette table est tout aussi simple :
var tableQuery = TableQuery.GenerateFilterCondition(nameof(TVProgram.Channel), QueryComparisons.Equal, this.channel);
var querySegment = await base.cloudTable
.ExecuteQuerySegmentedAsync<TVProgram>(tableQuery, new TableContinuationToken());
return querySegment.ToList();
Vous l’aurez donc remarqué, ce n’est pas plus compliqué d’utiliser un Azure Storage Table qu’une connexion SQL/Oracle/CosmosDB. Azure Function propose également des triggers sur Azure Storage Account permettant de déclencher vos fonctions sur des changements dans vos tables par exemple.
Je vous laisserai regarder mon repository Github où j’ai déposé le code source de cette Function App pour plus de détails sur comment sont codés les intents : kbeaugrand/TV-Guide-Webhook (github.com)
Configuration du Webhook dans Google Actions
Une fois votre Function App publiée, il vous faudra configurer l’url de ce web hook à Google Actions.
Par défaut les HTTP Endpoints sur Azure Function app sont soumises à une authentification au niveau de la fonction (clef passée en paramètre de l’URL). pour obtenir l’URL avec la clef d’authentification rendez-vous sur le détail de votre fonction dans votre portail Azure :

Cette URL est alors à fournir telle quelle dans la configuration de votre assistant Google :

Déploiement
Maintenant que nous avons tout publié et terminé la configuration de notre assistant Google Home, nous pouvons passer à la phase de tests. Rendez-vous sur l’onglet Tests de la console Google Actions :

Vous remarquerez que la console de test permet de tester en même temps votre assistant sur plusieurs types de plateformes (Smart Display, Téléphone, Haut parleur google Home, …) et qu’il est aisé de debugger dans la console de tests les appels Webhooks réalisés et leurs réponses obtenues depuis notre Fonction App.

Reste maintenant à publier notre assistant. En retournant sur l’onglet « Overview », vous serez guidé dans les différentes étapes de la publication de l’assistant (informations générales, cible de déploiement, …).
Analytics sur le fonctionnement des fonctions.
Maintenant que nos fonctions sont développées, nous pouvons nous brancher sur Azure App Insights pour bénéficier de ses fonctionnalités en matières d’Analytics et de Monitoring de notre fonction.



Conclusions
Je trouve la création d’un assistant conversationnel très simple avec Google Actions et Dialog Flow, effectivement il faut déjà avoir appréhendé les concepts de chatbot pour pouvoir y arriver, mais rien d’insurmontable. Les outils fournis dans la console google Actions rendent l’expérience de développement très agréables, une bonne surprise. L’intégration d’une Function Azure ne pose aucun problème grâce à l’emploi des webhooks. L’utilisation des Storage Account dans le cadre d’une Azure Fonction est naturelle avec le SDK C# fourni ou même en utilisant les bindings Azure Function qui rendent sont utilisation transparente.
Et surtout, maintenant je peux demander à mon Google Home qu’est-ce qu’il y a d’intéressant ce soir à la TV, souvent sa réponse est malheureusement décevante, mais n’est absolument pas lié, si à la qualité des outils employées ni à la qualité de ce que j’ai produit (enfin je crois).
P.S : Au moment où j’écris ce billet, la publication de mon assistant google Home est en cours de review par les équipes de Google Home et n’est disponible en test que sur les appareils où je suis connecté, je reviendrais ici vous informer lorsque l’assistant sera en ligne…

Un commentaire sur “Actions Google Home et Azure Functions”