Enregistrer les données d’utilisateur et de conversation
Article
S’APPLIQUE À : SDK v4
Un bot est par nature sans état. Une fois que votre bot est déployé, il ne peut pas s’exécuter dans le même processus ni sur le même ordinateur d’un tour à l’autre. Votre bot peut toutefois peut avoir besoin de suivre le contexte d’une conversation pour pouvoir gérer son comportement et se rappeler les réponses aux questions précédentes. Les fonctionnalités d’état et de stockage du SDK Bot Framework permettent d’ajouter un état à votre bot. Les bots utilisent la gestion de l’état et les objets de stockage pour gérer et conserver l’état. Le gestionnaire d’état fournit une couche d’abstraction qui vous permet d’accéder aux propriétés d’état à l’aide d’accesseurs de propriété, indépendamment du type de stockage sous-jacent.
Remarque
Les kits SDK JavaScript, C# et Python bot Framework continueront d’être pris en charge. Toutefois, le Kit de développement logiciel (SDK) Java est mis hors service avec une prise en charge finale à long terme se terminant en novembre 2023.
Seules les correctifs de sécurité et de bogues critiques au sein de ce référentiel seront entrepris.
Les bots existants créés avec le Kit de développement logiciel (SDK) Java continueront de fonctionner.
Le code présenté dans cet article est basé sur l’exemple de bot de gestion d’état. Vous aurez besoin d’une copie de l’exemple en C#, JavaScript, Java ou Python.
À propos de cet exemple
Quand il reçoit l’entrée utilisateur, cet exemple vérifie l’état de conversation stocké pour voir si cet utilisateur a précédemment été invité à fournir son nom. Si ce n’est pas le cas, le nom de l’utilisateur est demandé et cette entrée est stockée dans l’état utilisateur. Dans ce cas, le nom stocké dans l’état utilisateur est utilisé pour converser avec l’utilisateur et ses données d’entrée, ainsi que l’heure reçue et l’ID de canal d’entrée, est retourné à l’utilisateur. L’heure et les valeurs d’ID de canal sont récupérées à partir des données de conversation utilisateur, puis enregistrées dans l’état de la conversation. Le diagramme suivant montre la relation entre le bot, le profil utilisateur et les classes de données de conversation.
La première étape de la configuration de la gestion d’état consiste à définir les classes qui contiennent les informations à gérer dans l’état utilisateur et l’état de conversation. L’exemple utilisé dans cet article définit les classes suivantes :
Dans UserProfile.cs, vous définissez une UserProfile classe pour les informations utilisateur collectées par le bot.
Dans ConversationData.cs, vous définissez une ConversationData classe pour contrôler notre état de conversation lors de la collecte des informations utilisateur.
Les exemples de code suivants illustrent les définitions des classes UserProfile et ConversationData.
UserProfile.cs
public class UserProfile
{
public string Name { get; set; }
}
ConversationData.cs
public class ConversationData
{
// The time-stamp of the most recent incoming message.
public string Timestamp { get; set; }
// The ID of the user's channel.
public string ChannelId { get; set; }
// Track whether we have already asked the user's name
public bool PromptedUserForName { get; set; } = false;
}
Cette étape n’est pas nécessaire en JavaScript.
La première étape de la configuration de la gestion d’état consiste à définir les classes qui contiennent les informations à gérer dans l’état utilisateur et l’état de conversation. L’exemple utilisé dans cet article définit les classes suivantes :
Dans UserProfile.java, vous définissez une UserProfile classe pour les informations utilisateur collectées par le bot.
Dans ConversationData.java, vous définissez une ConversationData classe pour contrôler l’état de notre conversation lors de la collecte des informations utilisateur.
Les exemples de code suivants illustrent les définitions des classes UserProfile et ConversationData.
UserProfile.java
public class UserProfile {
private String name;
public String getName() {
return name;
}
public void setName(String withName) {
name = withName;
}
}
ConversationData.java
public class ConversationData {
// The time-stamp of the most recent incoming message.
private String timestamp;
// The ID of the user's channel.
private String channelId;
// Track whether we have already asked the user's name.
private boolean promptedUserForName = false;
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String withTimestamp) {
timestamp = withTimestamp;
}
public String getChannelId() {
return channelId;
}
public void setChannelId(String withChannelId) {
channelId = withChannelId;
}
public boolean getPromptedUserForName() {
return promptedUserForName;
}
public void setPromptedUserForName(boolean withPromptedUserForName) {
La première étape de la configuration de la gestion d’état consiste à définir les classes qui contiennent les informations à gérer dans l’état utilisateur et l’état de conversation. L’exemple utilisé dans cet article définit les classes suivantes :
Le fichier user_profile.py contient la UserProfile classe qui stocke les informations utilisateur collectées par le bot.
Le fichier conversation_data.py contient la ConversationData classe qui contrôle l’état de la conversation lors de la collecte des informations utilisateur.
Les exemples de code suivants illustrent les définitions des classes UserProfile et ConversationData.
user_profile.py
class UserProfile:
def __init__(self, name: str = None):
self.name = name
Ensuite, vous inscrivez MemoryStorage qui est utilisé pour créer et ConversationState objetsUserState. Les objets d’état utilisateur et de conversation sont créés lors de Startup et la dépendance est injectée dans le constructeur de bot. Les autres services inscrits qui disponibles pour un bot sont les suivants : un fournisseur d’informations d’identification, un adaptateur et l’implémentation de bot.
Startup.cs
// {
// TypeNameHandling = TypeNameHandling.All,
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state");
// With a custom JSON SERIALIZER, use this instead.
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state", jsonSerializer);
/* END AZURE BLOB STORAGE */
Ensuite, vous inscrivez MemoryStorage ce qui est ensuite utilisé pour créer et ConversationState objetsUserState. Ceux-ci sont créés dans index. js et sont consommés lors de la création du bot.
index.js
const memoryStorage = new MemoryStorage();
// Create conversation and user state with in-memory storage provider.
const conversationState = new ConversationState(memoryStorage);
const userState = new UserState(memoryStorage);
// Create the bot.
const bot = new StateManagementBot(conversationState, userState);
bots/stateManagementBot.js
const CONVERSATION_DATA_PROPERTY = 'conversationData';
const USER_PROFILE_PROPERTY = 'userProfile';
class StateManagementBot extends ActivityHandler {
constructor(conversationState, userState) {
super();
// Create the state property accessors for the conversation data and user profile.
this.conversationDataAccessor = conversationState.createProperty(CONVERSATION_DATA_PROPERTY);
this.userProfileAccessor = userState.createProperty(USER_PROFILE_PROPERTY);
// The state management objects for the conversation and user state.
this.conversationState = conversationState;
this.userState = userState;
Ensuite, vous inscrivez l’application.java StateManagementBot . ConversationState et UserState sont fournis par défaut à partir de la classe BotDependencyConfiguration, et Spring les injecte dans la méthode getBot.
Application.java
@Bean
public Bot getBot(
ConversationState conversationState,
UserState userState
) {
return new StateManagementBot(conversationState, userState);
}
Ensuite, vous inscrivez MemoryStorage qui est utilisé pour créer et ConversationState objetsUserState. Ceux-ci sont créés dans app.py et sont consommés lors de la création du bot.
app.py
CONVERSATION_STATE = ConversationState(MEMORY)
# Create Bot
BOT = StateManagementBot(CONVERSATION_STATE, USER_STATE)
# Listen for incoming requests on /api/messages.
bots/state_management_bot.py
def __init__(self, conversation_state: ConversationState, user_state: UserState):
if conversation_state is None:
raise TypeError(
"[StateManagementBot]: Missing parameter. conversation_state is required but None was given"
)
if user_state is None:
raise TypeError(
"[StateManagementBot]: Missing parameter. user_state is required but None was given"
)
self.conversation_state = conversation_state
self.user_state = user_state
self.conversation_data_accessor = self.conversation_state.create_property(
"ConversationData"
)
self.user_profile_accessor = self.user_state.create_property("UserProfile")
Vous créez maintenant des accesseurs de propriétés à l’aide de la CreateProperty méthode qui fournit un handle à l’objet BotState . Chaque accesseur de propriété d’état vous permet d’obtenir ou de définir la valeur de la propriété d’état associée. Avant d’utiliser les propriétés d’état, utilisez chaque accesseur pour charger la propriété à partir du stockage et l’obtenir à partir du cache d’état. Pour obtenir la clé correctement délimitée associée à la propriété d’état, vous appelez la GetAsync méthode.
Bots/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Vous créez maintenant des accesseurs de propriétés pour UserState et ConversationState. Chaque accesseur de propriété d’état vous permet d’obtenir ou de définir la valeur de la propriété d’état associée. Vous utilisez chaque accesseur pour charger la propriété associée à partir du stockage et récupérer son état actuel à partir du cache.
bots/stateManagementBot.js
// Create the state property accessors for the conversation data and user profile.
this.conversationDataAccessor = conversationState.createProperty(CONVERSATION_DATA_PROPERTY);
this.userProfileAccessor = userState.createProperty(USER_PROFILE_PROPERTY);
Vous créez maintenant des accesseurs de propriétés à l’aide de la createProperty méthode. Chaque accesseur de propriété d’état vous permet d’obtenir ou de définir la valeur de la propriété d’état associée. Avant d’utiliser les propriétés d’état, utilisez chaque accesseur pour charger la propriété à partir du stockage et l’obtenir à partir du cache d’état. Pour obtenir la clé correctement délimitée associée à la propriété d’état, vous appelez la get méthode.
Vous créez maintenant des accesseurs de propriétés pour UserProfile et ConversationData. Chaque accesseur de propriété d’état vous permet d’obtenir ou de définir la valeur de la propriété d’état associée. Vous utilisez chaque accesseur pour charger la propriété associée à partir du stockage et récupérer son état actuel à partir du cache.
La section précédente présente les étapes nécessaires au moment de l’initialisation pour ajouter des accesseurs de propriété d’état à notre bot. À présent, vous pouvez utiliser ces accesseurs au moment de l’exécution pour lire et écrire des informations d’état. L’exemple de code ci-dessous utilise le flux logique suivant :
Si userProfile.Name elle est vide et conversationData.PromptedUserForName true, vous récupérez le nom d’utilisateur fourni et stockez-le dans l’état de l’utilisateur.
Si userProfile.Name elle est vide et conversationData.PromptedUserForName est false, vous demandez le nom de l’utilisateur.
Si userProfile.Name vous avez été précédemment stocké, vous récupérez l’heure du message et l’ID de canal à partir de l’entrée utilisateur, renvoyez toutes les données à l’utilisateur et stockez les données récupérées dans l’état de conversation.
Bots/StateManagementBot.cs
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
// Get the state properties from the turn context.
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile());
if (string.IsNullOrEmpty(userProfile.Name))
{
// First time around this is set to false, so we will prompt user for name.
if (conversationData.PromptedUserForName)
{
// Set the name to what the user provided.
userProfile.Name = turnContext.Activity.Text?.Trim();
// Acknowledge that we got their name.
await turnContext.SendActivityAsync($"Thanks {userProfile.Name}. To see conversation data, type anything.");
// Reset the flag to allow the bot to go through the cycle again.
conversationData.PromptedUserForName = false;
}
else
{
// Prompt the user for their name.
await turnContext.SendActivityAsync($"What is your name?");
// Set the flag to true, so we don't prompt in the next turn.
conversationData.PromptedUserForName = true;
}
}
else
{
// Add message details to the conversation data.
// Convert saved Timestamp to local DateTimeOffset, then to string for display.
var messageTimeOffset = (DateTimeOffset)turnContext.Activity.Timestamp;
var localMessageTime = messageTimeOffset.ToLocalTime();
conversationData.Timestamp = localMessageTime.ToString();
conversationData.ChannelId = turnContext.Activity.ChannelId.ToString();
// Display state data.
await turnContext.SendActivityAsync($"{userProfile.Name} sent: {turnContext.Activity.Text}");
await turnContext.SendActivityAsync($"Message received at: {conversationData.Timestamp}");
await turnContext.SendActivityAsync($"Message received from: {conversationData.ChannelId}");
}
}
Avant de quitter le gestionnaire de tour, vous utilisez la méthode SaveChangesAsync() des objets de gestion d’état pour réécrire toutes les modifications d’état dans le stockage.
Bots/StateManagementBot.cs
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occurred during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}
Si userProfile.Name elle est vide et conversationData.PromptedUserForName true, vous récupérez le nom d’utilisateur fourni et stockez-le dans l’état de l’utilisateur.
Si userProfile.Name elle est vide et conversationData.PromptedUserForName est false, vous demandez le nom de l’utilisateur.
Si userProfile.Name vous avez été précédemment stocké, vous récupérez l’heure du message et l’ID de canal à partir de l’entrée utilisateur, renvoyez toutes les données à l’utilisateur et stockez les données récupérées dans l’état de conversation.
bots/stateManagementBot.js
this.onMessage(async (turnContext, next) => {
// Get the state properties from the turn context.
const userProfile = await this.userProfileAccessor.get(turnContext, {});
const conversationData = await this.conversationDataAccessor.get(
turnContext, { promptedForUserName: false });
if (!userProfile.name) {
// First time around this is undefined, so we will prompt user for name.
if (conversationData.promptedForUserName) {
// Set the name to what the user provided.
userProfile.name = turnContext.activity.text;
// Acknowledge that we got their name.
await turnContext.sendActivity(`Thanks ${ userProfile.name }. To see conversation data, type anything.`);
// Reset the flag to allow the bot to go though the cycle again.
conversationData.promptedForUserName = false;
} else {
// Prompt the user for their name.
await turnContext.sendActivity('What is your name?');
// Set the flag to true, so we don't prompt in the next turn.
conversationData.promptedForUserName = true;
}
} else {
// Add message details to the conversation data.
conversationData.timestamp = turnContext.activity.timestamp.toLocaleString();
conversationData.channelId = turnContext.activity.channelId;
// Display state data.
await turnContext.sendActivity(`${ userProfile.name } sent: ${ turnContext.activity.text }`);
await turnContext.sendActivity(`Message received at: ${ conversationData.timestamp }`);
await turnContext.sendActivity(`Message received from: ${ conversationData.channelId }`);
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
Avant de quitter chaque tour de dialogue, vous utilisez la méthode saveChanges() des objets de gestion d’état pour conserver toutes les modifications en écrivant l’état dans le stockage.
bots/stateManagementBot.js
/**
* Override the ActivityHandler.run() method to save state changes after the bot logic completes.
*/
async run(context) {
await super.run(context);
// Save any state changes. The load happened during the execution of the Dialog.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
}
Si userProfile.getName() elle est vide et conversationData.getPromptedUserForName() true, vous récupérez le nom d’utilisateur fourni et stockez-le dans l’état de l’utilisateur.
Si userProfile.getName() elle est vide et conversationData.getPromptedUserForName() est false, vous demandez le nom de l’utilisateur.
Si userProfile.getName() vous avez été précédemment stocké, vous récupérez l’heure du message et l’ID de canal à partir de l’entrée utilisateur, renvoyez toutes les données à l’utilisateur et stockez les données récupérées dans l’état de conversation.
StateManagementBot.java
@Override
protected CompletableFuture<Void> onMessageActivity(TurnContext turnContext) {
// Get state data from ConversationState.
StatePropertyAccessor<ConversationData> dataAccessor =
conversationState.createProperty("data");
CompletableFuture<ConversationData> dataFuture =
dataAccessor.get(turnContext, ConversationData::new);
// Get profile from UserState.
StatePropertyAccessor<UserProfile> profileAccessor = userState.createProperty("profile");
CompletableFuture<UserProfile> profileFuture =
profileAccessor.get(turnContext, UserProfile::new);
return dataFuture.thenCombine(profileFuture, (conversationData, userProfile) -> {
if (StringUtils.isBlank(userProfile.getName())) {
// First time around this is set to false, so we will prompt user for name.
if (conversationData.getPromptedUserForName()) {
// Reset the flag to allow the bot to go though the cycle again.
conversationData.setPromptedUserForName(false);
// Set the name to what the user provided and reply.
userProfile.setName(turnContext.getActivity().getText());
// Acknowledge that we got their name.
return turnContext.sendActivity(
MessageFactory.text(
"Thanks " + userProfile.getName()
+ ". To see conversation data, type anything."
)
);
} else {
// Set the flag to true, so we don't prompt in the next turn.
conversationData.setPromptedUserForName(true);
// Prompt the user for their name.
return turnContext.sendActivity(MessageFactory.text("What is your name?"));
}
} else {
OffsetDateTime messageTimeOffset = turnContext.getActivity().getTimestamp();
LocalDateTime localMessageTime = messageTimeOffset.toLocalDateTime();
//Displaying current date and time in 12 hour format with AM/PM
DateTimeFormatter dateTimeAMPMFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy, hh:mm:ss a");
conversationData.setTimestamp(dateTimeAMPMFormat.format(localMessageTime));
conversationData.setChannelId(turnContext.getActivity().getChannelId());
List<Activity> sendToUser = new ArrayList<>();
sendToUser.add(
MessageFactory.text(
userProfile.getName() + " sent: " + turnContext.getActivity().getText()
)
);
sendToUser.add(
MessageFactory.text("Message received at: " + conversationData.getTimestamp()
)
);
sendToUser.add(
MessageFactory.text("Message received from: " + conversationData.getChannelId()
)
);
return turnContext.sendActivities(sendToUser);
}
})
// make the return value happy.
.thenApply(resourceResponse -> null);
}
Avant de quitter le gestionnaire de tour, vous utilisez la méthode saveChanges() des objets de gestion d’état pour réécrire toutes les modifications d’état dans le stockage.
StateManagementBot.java
@Override
public CompletableFuture<Void> onTurn(TurnContext turnContext) {
return super.onTurn(turnContext)
// Save any state changes that might have occurred during the turn.
.thenCompose(turnResult -> conversationState.saveChanges(turnContext))
.thenCompose(saveResult -> userState.saveChanges(turnContext));
}
Si user_profile.name est vide et si conversation_data.prompted_for_user_name a la valeur true, le bot récupère le nom fourni par l’utilisateur et le stocke dans l’état de l’utilisateur.
S’il user_profile.name est vide et conversation_data.prompted_for_user_name est false, le bot demande le nom de l’utilisateur.
S’il user_profile.name a été précédemment stocké, le bot récupère l’heure du message et l’ID de canal à partir de l’entrée utilisateur, renvoie les données à l’utilisateur et stocke les données récupérées dans l’état de la conversation.
bots/state_management_bot.py
async def on_message_activity(self, turn_context: TurnContext):
# Get the state properties from the turn context.
user_profile = await self.user_profile_accessor.get(turn_context, UserProfile)
conversation_data = await self.conversation_data_accessor.get(
turn_context, ConversationData
)
if user_profile.name is None:
# First time around this is undefined, so we will prompt user for name.
if conversation_data.prompted_for_user_name:
# Set the name to what the user provided.
user_profile.name = turn_context.activity.text
# Acknowledge that we got their name.
await turn_context.send_activity(
f"Thanks { user_profile.name }. To see conversation data, type anything."
)
# Reset the flag to allow the bot to go though the cycle again.
conversation_data.prompted_for_user_name = False
else:
# Prompt the user for their name.
await turn_context.send_activity("What is your name?")
# Set the flag to true, so we don't prompt in the next turn.
conversation_data.prompted_for_user_name = True
else:
# Add message details to the conversation data.
conversation_data.timestamp = self.__datetime_from_utc_to_local(
turn_context.activity.timestamp
)
conversation_data.channel_id = turn_context.activity.channel_id
# Display state data.
await turn_context.send_activity(
f"{ user_profile.name } sent: { turn_context.activity.text }"
)
await turn_context.send_activity(
f"Message received at: { conversation_data.timestamp }"
)
await turn_context.send_activity(
f"Message received from: { conversation_data.channel_id }"
)
Avant chaque tour de dialogue, le bot utilise la méthode des save_changes objets de gestion d’état pour conserver toutes les modifications en écrivant des informations d’état dans le stockage.
Exécutez l’exemple en local sur votre machine.
Si vous avez besoin d’instructions, reportez-vous au fichier README pour C#, JavaScript, Java ou Python.
Utilisez l’émulateur pour tester votre exemple de bot.
Informations supplémentaires
Cet article décrit comment ajouter un état à votre bot. Pour plus d’informations sur les rubriques connexes, consultez le tableau suivant.
Tous les appels de gestion d’état sont asynchrones et, par défaut, le dernier qui écrit gagne (last-writer-wins). Dans la pratique, vous devez obtenir, définir et enregistrer l’état dans votre bot de la façon la plus rapprochée possible. Pour une discussion sur la façon d’implémenter le verrouillage optimiste, consultez Implémenter un stockage personnalisé pour votre bot.
Données métier critiques
Utilisez l’état du bot pour stocker les préférences, le nom d’utilisateur ou la dernière chose qu’ils ont ordonnée, mais ne l’utilisez pas pour stocker les données métier critiques. Pour les données critiques, créez vos propres composants de stockage ou écrivez directement dans le stockage.
Recognizer-Text
L’exemple utilise les bibliothèques Microsoft/Recognizers-Text pour analyser et valider les entrées utilisateur. Pour plus d’informations, consultez la page de présentation.
Étapes suivantes
Découvrez comment poser à l’utilisateur une série de questions, valider ses réponses et enregistrer son entrée.