Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Bot Builder V4 pour .NET : gestion du Bot State et sauvegarde dans Azure CosmosDB,
Un billet d'Hinault Romaric

Le , par Hinault Romaric

0PARTAGES

Le Bot Builder SDK V4 est un ensemble d’outils permettant aux développeurs de créer des agents conversationnels (bot) en mesure d’échanger avec un tiers.

Un bot dispose d’un état (State). Dans cet état sont sauvegardées des données liées à une conversation, un utilisateur, etc. Ces données sont en quelques sorte un log des interactions entre le bot et les utilisateurs. Les données d’état sont utilisées par exemple par le Bot pour se souvenir des réponses aux questions précédentes ou pour prendre des décisions. Ces données peuvent être exploitées par le développeur pour améliorer l’expérience utilisateur dans divers scénarios. Vous pouvez utiliser par exemple les données d’état du bot pour afficher un message de bienvenue personnalisé à un utilisateur régulier, customiser l’expérience en fonction des préférences de l’utilisateur, redémarrer une conversation avec un utilisateur, etc.

Par nature, le bot est stateless (sans état). Cela veut dire qu’il ne sauvegarde pas les données ou le statut de la communication pour chaque session. Par ailleurs, lorsque le bot est déployé, il ne s’exécute pas dans le même processus ou sur le même ordinateur d’une conversation à une autre.

Le SDK du Bot Framework offre des fonctionnalités de gestion d’état et de stockage permettant d’intégrer avec souplesse la prise en charge du State à votre bot. Dans les modèles de projets offerts par le SDK, le State est déjà mis en place pour permettre au bot de gérer le flux de conversation.

Toutefois, si vous avez des besoins particuliers qui peuvent entrainer la manipulation des données supplémentaires dans l’état du bot, vous devez maitriser certains concepts de base de la gestion d’état dans un bot pour être en mesure de le faire.

Dans ce billet, nous verrons dans un premier temps les éléments qui entrent en jeux dans la gestion des états et les configurations à apporter pour intégrés des entités personnalisées au State.

Dans un second temps, nous verrons comment stoker les données d’état dans une base de données. Pour notre cas, nous allons utiliser CosmosDB de Microsoft Azure.

Les entités d’État

Pour intégrer le support de l’état à votre bot, vous devez dans un premier temps définir la structure de vos données (Entités qui seront utilisées pour accéder aux données). Par défaut, le SDK implémente les suivants :

  • ConversationState : permets au bot d’effectuer le suivi de la conversation en cours entre lui-même et l’utilisateur;
  • UserState : peut servir à de nombreuses fins, par exemple pour déterminer l’endroit où une conversation précédente de l’utilisateur s’était arrêtée ou simplement pour accueillir un utilisateur régulier par son nom. Si vous stockez les préférences d’un utilisateur, vous pouvez utiliser ces informations pour personnaliser la prochaine conversation;
  • DialogState : permets au bot d’effectuer un suivi dans la pile de dialogues.


Vous pouvez définir vos propres entités en fonction de vos besoins. Dans l’exemple de mon billet précédent, j’avais ajouté une entité pour permettre au bot de se souvenir des réponses que l’utilisateur allait donner à chaque étape.

Code c# : Sélectionner tout
1
2
3
4
5
6
public class FeedbackData 
    { 
        public string Name { get; set; } 
        public string Email { get; set; } 
        public string Message { get; set; } 
    }

C’est un exemple d’entité d’état.

Les accesseurs

La seconde chose à faire est la définition d’un accesseur. En effet, le Bot aura besoin de ce dernier pour manipuler les propriétés d’état. Les accesseurs offrent des méthodes de lecture, écriture et suppression pour accéder et manipuler les propriétés d’état lors d’un tour de communication.

La classe que vous allez définir comme accesseur doit donc avoir des propriétés pour toutes les entités d’état qui sont manipulées par votre bot. En dehors des entités d’état par défaut du framework, vos propres entités d’état doivent être définies comme suit :

Code c# : Sélectionner tout
public IStatePropertyAccessor<FeedbackData> FeedbackData { get; set; }

IStatePropertyAccessor définit des méthodes asynchrones qui permettront à l’accesseur de lire, modifier ou supprimer une propriété d’état.

Ci-dessous l’exemple de code complet de l’accesseur que nous avons utilisé dans l’exemple du billet précédent :

Code c# : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class BotAccessors 
    { 
        /// <summary> 
        /// Initializes a new instance of the <see cref="BotAccessors"/> class. 
        /// Contains the <see cref="ConversationState"/> and associated <see cref="IStatePropertyAccessor{T}"/>. 
        /// </summary> 
        /// <param name="conversationState">The state object that stores the conversationState.</param> 
        /// <param name="userState">The state object that stores the userState.</param> 
        public BotAccessors(ConversationState conversationState, UserState userState) 
        { 
            ConversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState)); 
            UserState = userState ?? throw new ArgumentNullException(nameof(userState)); 
        } 
  
        /// <summary> 
        /// Gets or sets the <see cref="IStatePropertyAccessor{T}"/> for FeedbackData. 
        /// </summary> 
        /// <value> 
        /// The accessor stores the FeedbackData for the conversation. 
        /// </value> 
        public IStatePropertyAccessor<FeedbackData> FeedbackData { get; set; } 
  
  
        /// <summary> 
        /// Gets or sets the <see cref="IStatePropertyAccessor{T}"/> for ConversationDialogState. 
        /// </summary> 
        /// <value> 
        /// The accessor stores the ConversationDialogState for the conversation. 
        /// </value> 
        public IStatePropertyAccessor<DialogState> ConversationDialogState { get; set; } 
  
  
        /// <summary> 
        /// Gets the <see cref="ConversationState"/> object for the conversation. 
        /// </summary> 
        /// <value>The <see cref="ConversationState"/> object.</value> 
        public ConversationState ConversationState { get; } 
  
        /// <summary> 
        /// Gets the <see cref="UserState"/> object for the conversation. 
        /// </summary> 
        /// <value>The <see cref="UserState"/> object.</value> 
        public UserState UserState { get; } 
    }

Configuration du bot

L’accesseur est ajouté au conteneur d’IoC comme un Singleton, car c’est la même instance de l’accesseur qui est utilisé par le bot pendant son exécution. Cela est fait dans la méthode ConfigureService du Startup.cs.

À la création de l’accesseur, dans le AddSigngleton(), vous devez initialiser les propriétés de vos états, en dehors du ConversationState et du UserState dont l’initialisation est effectuée par le bot.

Selon la documentation de Microsoft, vos propriétés d’état personnalisées doivent être liées au UserState ou au ConversationState, en fonction de votre besoin. Pour des données associées à chaque utilisateur, vous devez utiliser le UserState. À ce moment, votre état personnalisé sera sauvegardé comme une propriété du UserState.

Ci-dessous le code utilisé dans mon exemple pour enregistrer le FeedbackData comme une propriété du UserState.

Code c# : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
services.AddSingleton<BotAccessors>(sp => 
            { 
                // …. 
  
                // Create the custom state accessor. 
                // State accessors enable other components to read and write individual properties of state. 
                var accessors = new BotAccessors(conversationState, userState) 
                { 
                    ConversationDialogState = conversationState.CreateProperty<DialogState>("DialogState"), 
                    FeedbackData = userState.CreateProperty<FeedbackData>("FeedbackData"), 
                }; 
  
                return accessors; 
            });

Le UserState et le ConversationState sont initialisés lors de l’ajout du bot dans le conteneur d’injection de dépendance de ASP.NET Core :

Code c# : Sélectionner tout
1
2
3
4
5
6
7
8
9
  // Create Conversation State object. 
                // The Conversation State object is where we persist anything at the conversation-scope. 
                var conversationState = new ConversationState(dataStore); 
  
                options.State.Add(conversationState); 
  
                // Create and add user state. 
                var userState = new UserState(dataStore); 
                options.State.Add(userState);

Accéder et sauvegarder les données d’état dans le bot

Regardons maintenant comment nous pouvons manipuler les données d’état dans notre bot.
Dans un premier temps, dans le constructeur du bot, vous devez passer en paramètre l’accesseur du Bot State :

Code c# : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RequestFeedbackDialog : IBot 
    { 
        private readonly BotAccessors _accessors; 
        private readonly ILogger _logger; 
  
  
        public RequestFeedbackDialog(BotAccessors accessors, ILoggerFactory loggerFactory) 
        { 
  
            _accessors = accessors ?? throw new ArgumentNullException(nameof(accessors)); 
  
           //Code d'initialisation du bot 
  
        } 
  }

Pour utiliser la propriété FeedBackData, nous devons utiliser l’accesseur pour charger et récupérer cette dernière dans le cache d’état. Ensuite, nous pouvons effectuer toute mise à jour :

Code c# : Sélectionner tout
1
2
3
4
5
 // Get the current feedbackData object from user state. 
            var feedbackData = await _accessors.FeedbackData.GetAsync(stepContext.Context, () => new FeedbackData(), cancellationToken); 
  
            // Update the feedbackData. 
            feedbackData.Name = (string)stepContext.Result;

Avant de quitter le gestionnaire de tour (OnTurnAsync), nous devons sauvegarder les modifications dans notre système de stockage. Cela se fait en utilisant la méthode SaveChangesAsync() du UserState et ConversationState :

Code c# : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)) 
        { 
            //..... 
  
            // Save the dialog state into the conversation state. 
            await _accessors.ConversationState.SaveChangesAsync(turnContext, false, cancellationToken); 
  
            // Save the  FeedbackData updates into the user state. 
            await _accessors.UserState.SaveChangesAsync(turnContext, false, cancellationToken); 
  
        }

Sauvegarde des données d’état dans Azure CosmosDB

Par défaut, le SDK implémente une mémoire volatile pour la sauvegarde des données du state :

Code c# : Sélectionner tout
  IStorage dataStore = new MemoryStorage();

Sauf que cette mémoire persiste uniquement le temps d’exécution du bot. Il est recommandé de ne pas l’utiliser en environnement de production. Cette option a été offerte pour permettre uniquement aux développeurs de faire fonctionner leur bot en environnement de développement.

En fonction de la plateforme de stockage qu’ils souhaitent utiliser, les développeurs doivent implémenter leur propre « State Client ». Pour ceux qui utilisent Azure, Microsoft offre l’extension Bot.Builder.Azure. Cette extension implémente le « State Client » pour Azure Table Storage (TableBotdataStore), Azure SQL Server (SqlBotDataStore) et DocumentDbBotDataStore pour Azure CosmoDB.

Dans cette seconde partie de ce billet, nous verrons comment sauvegarder le bot state dans Azure CosmosDB.

Pour commencer, vous devez créer une base de données CosmosDB sur Azure. Vous pouvez consulter le tutoriel suivant pour savoir comment le faire : https://docs.microsoft.com/en-us/azu...dotnet-preview

Une fois la base de données CosmosDB créée, vous devez ajouter une collection à cette dernière. Pour savoir comment le faire, consultez le tutoriel suivant : https://docs.microsoft.com/en-us/azu...dotnet-preview

Microsoft offre le package Microsoft.Bot.Builder.Azure pour faciliter l’utilisation des services Azure dans le Bot. Ce package est disponible sur NuGet. Vous devez installer ce dernier :


Une fois le package installé vous devez définir dans le fichier de configuration de votre application (appsettings), les informations nécessaires pour accéder à votre instance CosmosDB. Vous devez donc renseigner le nom de la base de données (cosmosDBDtatbaseName), le nom de votre collection (cosmosDBCollectionName), la clé de votre instance CosmosDB (cosmosDBKey) et enfin l’adresse URL permettant de se connecter à votre instance CosmosDB (cosmosServiceEndpoint) :

Code json : Sélectionner tout
1
2
3
4
5
6
7
8
{ 
  "botFilePath": "BotConfiguration.bot", 
  "botFileSecret": "", 
  "cosmosServiceEndpoint": "https://xxx.documents.azure.com:443/", 
  "cosmosDBKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 
  "cosmosDBDatabaseName": "Bot", 
  "cosmosDBCollectionName": "FeedBack" 
}

La clé et l’URL du service sont disponibles dans la section Keys des paramètres de votre service CosmosDB dans le portail Azure :


Pour finir, vous devez modifier le fichier Startup.cs et remplacer :

Code c# : Sélectionner tout
IStorage dataStore = new MemoryStorage();

Par
Code c# : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  services.AddBot<RequestFeedbackDialog>(options => 
            { 
  
  
                // …. 
  
                IStorage dataStore = new CosmosDbStorage(new CosmosDbStorageOptions 
                { 
                    AuthKey = Configuration.GetSection("cosmosDBKey")?.Value, 
                    CollectionId = Configuration.GetSection("cosmosDBCollectionName")?.Value, 
                    CosmosDBEndpoint = new Uri(Configuration.GetSection("cosmosServiceEndpoint")?.Value), 
                    DatabaseId = Configuration.GetSection("cosmosDBDatabaseName")?.Value, 
                });  
  
                // Create Conversation State object. 
                // The Conversation State object is where we persist anything at the conversation-scope. 
                var conversationState = new ConversationState(dataStore); 
  
                options.State.Add(conversationState); 
  
                // Create and add user state. 
                var userState = new UserState(dataStore); 
                options.State.Add(userState); 
            });

Après avoir exécuté votre bot, vous remarquerez que les données collectées par le bot lors d’une conversation sont sauvegardées sur votre instance CosmosDB.


Il est important de savoir comment fonctionne l’état dans un bot et comment stocker les données d’états, si vous voulez mettre en place un bot un peu complexe. Ce billet vous a présenté sommairement ce qu’il faut savoir sur le Bot state et comment configurer et sauvegarder le state dans une instance CosmosDB.

Restez connecté pour d’autres billets sur le Bot Builder SDK V4.

Le code complet de cet exemple sur mon GitHub

Bot Builder V4 : mise en place d’une conversation guidée avec la librairie Dialogs

Démarrer avec le Bot Builder SDK V4 pour .NET

Bot Framework : exploiter les fonctionnalités du SDK V3 dans la version 4 du Bot Builder

Documentation officielle du SDK V4

GitHub du Bot Builder SDK V4

GitHub du Bot Framework Emulator V4

Blog Bot Framework

Une erreur dans cette actualité ? Signalez-le nous !