Save user and conversation data (Guardar dados da conversação e do utilizador)
Artigo
APLICA-SE A: SDK v4
Um bot é inerentemente apátrida. Depois que o bot é implantado, ele pode não ser executado no mesmo processo ou na mesma máquina de um turno para o outro. No entanto, seu bot pode precisar rastrear o contexto de uma conversa para que ele possa gerenciar seu comportamento e lembrar as respostas a perguntas anteriores. Os recursos de estado e armazenamento do SDK do Bot Framework permitem que você adicione estado ao seu bot. Os bots usam gerenciamento de estado e objetos de armazenamento para gerenciar e persistir o estado. O gerenciador de estado fornece uma camada de abstração que permite acessar propriedades de estado usando acessadores de propriedade, independentemente do tipo de armazenamento subjacente.
Nota
Os SDKs JavaScript, C# e Python do Bot Framework continuarão a ser suportados, no entanto, o Java SDK está sendo desativado com suporte final de longo prazo terminando em novembro de 2023.
Os bots existentes construídos com o Java SDK continuarão a funcionar.
O código neste artigo é baseado no exemplo de Bot de Gerenciamento de Estado. Você precisará de uma cópia do exemplo em C#, JavaScript, Java ou Python.
Sobre este exemplo
Ao receber a entrada do usuário, este exemplo verifica o estado da conversa armazenada para ver se esse usuário foi solicitado anteriormente a fornecer seu nome. Caso contrário, o nome do usuário é solicitado e essa entrada é armazenada no estado do usuário. Em caso afirmativo, o nome armazenado no estado do usuário é usado para conversar com o usuário e seus dados de entrada, juntamente com o tempo recebido e o ID do canal de entrada, são retornados ao usuário. Os valores de ID de hora e canal são recuperados dos dados de conversa do usuário e, em seguida, salvos no estado de conversa. O diagrama a seguir mostra a relação entre o bot, o perfil de usuário e as classes de dados de conversação.
A primeira etapa na configuração do gerenciamento de estado é definir as classes que contêm as informações a serem gerenciadas no estado de usuário e conversação. O exemplo usado neste artigo define as seguintes classes:
No UserProfile.cs, você define uma UserProfile classe para as informações do usuário que o bot coletará.
Além ConversationData.cs, você define uma ConversationData classe para controlar nosso estado de conversação enquanto coleta informações do usuário.
Os exemplos de código a seguir mostram as definições para as UserProfile classes e 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;
}
Esta etapa não é necessária em JavaScript.
A primeira etapa na configuração do gerenciamento de estado é definir as classes que contêm as informações a serem gerenciadas no estado de usuário e conversação. O exemplo usado neste artigo define as seguintes classes:
No UserProfile.java, você define uma UserProfile classe para as informações do usuário que o bot coletará.
Além ConversationData.java, você define uma ConversationData classe para controlar nosso estado de conversação enquanto coleta informações do usuário.
Os exemplos de código a seguir mostram as definições para as UserProfile classes e 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) {
A primeira etapa na configuração do gerenciamento de estado é definir as classes que contêm as informações a serem gerenciadas no estado de usuário e conversação. O exemplo usado neste artigo define as seguintes classes:
O user_profile.py contém a classe que armazena UserProfile as informações do usuário coletadas pelo bot.
O conversation_data.py contém a classe que controla ConversationData o estado da conversação enquanto coleta informações do usuário.
Os exemplos de código a seguir mostram as definições para as UserProfile classes e ConversationData .
user_profile.py
class UserProfile:
def __init__(self, name: str = None):
self.name = name
Em seguida, você registra MemoryStorage que é usado para criar UserState e ConversationState objetos. Os objetos de estado de usuário e conversação são criados em Startup e a dependência é injetada no construtor do bot. Outros serviços para um bot que estão registrados são: um provedor de credenciais, um adaptador e a implementação do 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 */
Em seguida, você registra MemoryStorage que é usado para criar UserState e ConversationState objetos. Eles são criados em index.js e consumidos quando o bot é criado.
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;
Em seguida, você registra o StateManagementBot in Application.java. ConversationState e UserState são fornecidos por padrão da classe BotDependencyConfiguration, e o Spring os injetará no método getBot.
Application.java
@Bean
public Bot getBot(
ConversationState conversationState,
UserState userState
) {
return new StateManagementBot(conversationState, userState);
}
Em seguida, você registra MemoryStorage que é usado para criar UserState e ConversationState objetos. Eles são criados em app.py e consumidos quando o bot é criado.
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")
Agora você cria acessadores de propriedade usando o CreateProperty método que fornece um identificador para o BotState objeto. Cada acessador de propriedade de estado permite que você obtenha ou defina o valor da propriedade de estado associada. Antes de usar as propriedades de estado, use cada acessador para carregar a propriedade do armazenamento e obtê-la do cache de estado. Para obter a chave com escopo adequado associada à propriedade state, chame o GetAsync método.
Bots/StateManagementBot.cs
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
Agora você cria acessadores de propriedade para UserState e ConversationState. Cada acessador de propriedade de estado permite que você obtenha ou defina o valor da propriedade de estado associada. Você usa cada acessador para carregar a propriedade associada do armazenamento e recuperar seu estado atual do 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);
Agora você cria acessadores de propriedade usando o createProperty método. Cada acessador de propriedade de estado permite que você obtenha ou defina o valor da propriedade de estado associada. Antes de usar as propriedades de estado, use cada acessador para carregar a propriedade do armazenamento e obtê-la do cache de estado. Para obter a chave com escopo adequado associada à propriedade state, chame o get método.
Agora você cria acessadores de propriedade para UserProfile e ConversationData. Cada acessador de propriedade de estado permite que você obtenha ou defina o valor da propriedade de estado associada. Você usa cada acessador para carregar a propriedade associada do armazenamento e recuperar seu estado atual do cache.
A seção anterior aborda as etapas de tempo de inicialização para adicionar acessadores de propriedade de estado ao nosso bot. Agora, você pode usar esses acessadores em tempo de execução para ler e gravar informações de estado. O código de exemplo abaixo usa o seguinte fluxo lógico:
Se userProfile.Name estiver vazio e conversationData.PromptedUserForName for true, você recuperará o nome de usuário fornecido e armazená-lo no estado do usuário.
Se userProfile.Name estiver vazio e conversationData.PromptedUserForName for falso, você pede o nome do usuário.
Se userProfile.Name tiver sido armazenado anteriormente, você recupera o tempo da mensagem e o ID do canal da entrada do usuário, ecoa todos os dados de volta para o usuário e armazena os dados recuperados no estado da conversa.
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}");
}
}
Antes de sair do manipulador de turnos, use o método SaveChangesAsync() dos objetos de gerenciamento de estado para gravar todas as alterações de estado de volta ao armazenamento.
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);
}
Se userProfile.Name estiver vazio e conversationData.PromptedUserForName for true, você recuperará o nome de usuário fornecido e armazená-lo no estado do usuário.
Se userProfile.Name estiver vazio e conversationData.PromptedUserForName for falso, você pede o nome do usuário.
Se userProfile.Name tiver sido armazenado anteriormente, você recupera o tempo da mensagem e o ID do canal da entrada do usuário, ecoa todos os dados de volta para o usuário e armazena os dados recuperados no estado da conversa.
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();
});
Antes de sair de cada turno de diálogo, use o método saveChanges() dos objetos de gerenciamento de estado para persistir todas as alterações gravando o estado novamente no armazenamento.
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);
}
Se userProfile.getName() estiver vazio e conversationData.getPromptedUserForName() for true, você recuperará o nome de usuário fornecido e armazená-lo no estado do usuário.
Se userProfile.getName() estiver vazio e conversationData.getPromptedUserForName() for falso, você pede o nome do usuário.
Se userProfile.getName() tiver sido armazenado anteriormente, você recupera o tempo da mensagem e o ID do canal da entrada do usuário, ecoa todos os dados de volta para o usuário e armazena os dados recuperados no estado da conversa.
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);
}
Antes de sair do manipulador de turnos, use o método saveChanges() dos objetos de gerenciamento de estado para gravar todas as alterações de estado de volta ao armazenamento.
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));
}
Se user_profile.name estiver vazio e conversation_data.prompted_for_user_name for true, o bot recuperará o nome fornecido pelo usuário e o armazenará no estado do usuário.
Se user_profile.name estiver vazio e conversation_data.prompted_for_user_name for falso, o bot pede o nome do usuário.
Se user_profile.name tiver sido armazenado anteriormente, o bot recupera o tempo da mensagem e o ID do canal da entrada do usuário, ecoa os dados de volta para o usuário e armazena os dados recuperados no estado da conversa.
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 }"
)
Antes de cada volta de diálogo terminar, o bot usa o método dos save_changes objetos de gerenciamento de estado para persistir todas as alterações gravando informações de estado no armazenamento.
Execute a amostra localmente na sua máquina.
Se precisar de instruções, consulte o LEIA-ME para C#, JavaScript, Java ou Python.
Use o emulador para testar seu bot de exemplo.
Informações adicionais
Este artigo descreveu como você pode adicionar estado ao seu bot. Consulte a tabela a seguir para obter mais informações sobre tópicos relacionados.
Tópico
Notas
Privacidade
Caso pretenda armazenar dados pessoais do utilizador, deverá assegurar o cumprimento do Regulamento Geral de Proteção de Dados.
Gestão de estados
Todas as chamadas de gerenciamento de estado são assíncronas e o último gravador ganha por padrão. Na prática, você deve obter, definir e salvar o estado o mais próximo possível em seu bot. Para obter uma discussão sobre como implementar o bloqueio otimista, consulte Implementar armazenamento personalizado para seu bot.
Dados críticos para os negócios
Use o estado do bot para armazenar preferências, nome de usuário ou a última coisa que eles pediram, mas não o use para armazenar dados corporativos críticos. Para dados críticos, crie seus próprios componentes de armazenamento ou grave diretamente no armazenamento.
Reconhecedor-Texto
O exemplo usa as bibliotecas Microsoft/Recognizers-Text para analisar e validar a entrada do usuário. Para obter mais informações, consulte a página de visão geral .
Próximos passos
Saiba como fazer uma série de perguntas ao usuário, validar suas respostas e salvar suas entradas.