Expirar uma conversa

APLICA-SE A: SDK v4

Às vezes, um bot precisa reiniciar uma conversa desde o início. Por exemplo, se o usuário não responder após um determinado período. Este artigo descreve dois métodos para expirar uma conversa:

  • Acompanhar a última vez que uma mensagem foi recebida de um usuário e limpar o estado se o tempo for maior que um comprimento pré-configurado ao ser recebida a próxima mensagem do usuário. Para obter mais informações, confira a seção expiração da Interação do usuário.
  • Use um recurso de camada de armazenamento, como o recurso de vida útil (TTL) do Cosmos DB, para limpar automaticamente o estado após um período pré-configurado. Para obter mais informações, confira a seção expiração do armazenamento.

Observação

Os SDKs do Bot Framework para JavaScript, C# e Python continuarão a ter suporte, no entanto, o SDK para Java está sendo desativado. Seu suporte final de longo prazo será encerrado em novembro de 2023. Somente correções críticas de segurança e de bugs serão realizadas neste repositório.

Os bots existentes criados com o SDK para Java continuarão a funcionar.

Para a criação de novos bots, considere usar o Power Virtual Agents e leia sobre como escolher a solução de chatbot mais adequada.

Para obter mais informações, confira O futuro da criação de bots.

Pré-requisitos

Sobre este exemplo

O código de exemplo deste artigo começa com a estrutura de um bot de várias rodadas e estende a funcionalidade deste bot ao adicionar códigos (fornecidos nas seções a seguir). Esse código estendido demonstra como limpar o estado da conversa após um determinado período.

Expiração da interação do usuário

Esse tipo de conversa que expira é obtido ao ser adicionada uma propriedade de hora do último acesso ao estado de conversa do bot. Esse valor da propriedade é, em seguida, comparado com a hora atual dentro do manipulador de atividades antes do processamento das atividades.

Observação

Este exemplo usa um tempo limite de 30 segundos para facilitar o teste desse padrão.

appsettings.json

Primeiro, adicione uma configuração ExpireAfterSeconds a appsettings.json:

{
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",
  "ExpireAfterSeconds": 30
}

Bots\DialogBot.cs

Em seguida, adicione campos ExpireAfterSeconds, LastAccessedTimeProperty e DialogStateProperty à classe do bot e inicialize-os no construtor do bot. Adicione também um parâmetro IConfiguration ao construtor com o qual valor ExpireAfterSeconds será recuperado.

Em vez de criar o acessador embutido da propriedade de estado do diálogo no método OnMessageActivityAsync, você o está criando e registrando no momento da inicialização. O bot precisará do acessador da propriedade de estado não apenas para executar o diálogo, mas também para limpar o estado do diálogo.

protected readonly int ExpireAfterSeconds;
protected readonly IStatePropertyAccessor<DateTime> LastAccessedTimeProperty;
protected readonly IStatePropertyAccessor<DialogState> DialogStateProperty;

// Existing fields omitted...

public DialogBot(IConfiguration configuration, ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
{
    ConversationState = conversationState;
    UserState = userState;
    Dialog = dialog;
    Logger = logger;

    ExpireAfterSeconds = configuration.GetValue<int>("ExpireAfterSeconds");
    DialogStateProperty = ConversationState.CreateProperty<DialogState>(nameof(DialogState));
    LastAccessedTimeProperty = ConversationState.CreateProperty<DateTime>(nameof(LastAccessedTimeProperty));
}

Por fim, adicione código ao método OnTurnAsync do bot para limpar o estado do diálogo se a conversa for muito antiga.

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
    // Retrieve the property value, and compare it to the current time.
    var lastAccess = await LastAccessedTimeProperty.GetAsync(turnContext, () => DateTime.UtcNow, cancellationToken).ConfigureAwait(false);
    if ((DateTime.UtcNow - lastAccess) >= TimeSpan.FromSeconds(ExpireAfterSeconds))
    {
        // Notify the user that the conversation is being restarted.
        await turnContext.SendActivityAsync("Welcome back!  Let's start over from the beginning.").ConfigureAwait(false);

        // Clear state.
        await ConversationState.ClearStateAsync(turnContext, cancellationToken).ConfigureAwait(false);
    }

    await base.OnTurnAsync(turnContext, cancellationToken).ConfigureAwait(false);

    // Set LastAccessedTime to the current time.
    await LastAccessedTimeProperty.SetAsync(turnContext, DateTime.UtcNow, cancellationToken).ConfigureAwait(false);

    // Save any state changes that might have occurred during the turn.
    await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken).ConfigureAwait(false);
    await UserState.SaveChangesAsync(turnContext, false, cancellationToken).ConfigureAwait(false);
}

Expiração do armazenamento

O Cosmos DB fornece um recurso de vida útil (TTL) que permite que você exclua itens automaticamente de um contêiner após um determinado período. Isso pode ser configurado de dentro do portal do Azure ou durante a criação do contêiner (usando os SDKs do Cosmos DB específicos da linguagem).

O SDK do Bot Framework não expõe a definição de configuração de um TTL. No entanto, a inicialização do contêiner pode ser substituída e o SDK do Cosmos DB pode ser usado para configurar a TTL antes da inicialização do armazenamento do Bot Framework.

Comece com uma nova cópia do exemplo de solicitação de várias rodadas e adicione o pacote NuGet Microsoft.Bot.Builder.Azure ao projeto.

appsettings.json

Atualize appsettings.json para incluir opções de armazenamento do Cosmos DB:

{
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",

  "CosmosDbTimeToLive": 30,
  "CosmosDbEndpoint": "<endpoint-for-your-cosmosdb-instance>",
  "CosmosDbAuthKey": "<your-cosmosdb-auth-key>",
  "CosmosDbDatabaseId": "<your-database-id>",
  "CosmosDbUserStateContainerId": "<no-ttl-container-id>",
  "CosmosDbConversationStateContainerId": "<ttl-container-id>"
}

Observe os dois ContainerIds, um para UserState e outro para ConversationState. A TTL padrão é definida no contêiner ConversationState, mas não no UserState.

CosmosDbStorageInitializerHostedService.cs

Em seguida, crie uma classe CosmosDbStorageInitializerHostedService, que criará o contêiner com a vida útil configurada.

// Add required using statements...

public class CosmosDbStorageInitializerHostedService : IHostedService
{
    readonly CosmosDbPartitionedStorageOptions _storageOptions;
    readonly int _cosmosDbTimeToLive;

    public CosmosDbStorageInitializerHostedService(IConfiguration config)
    {
        _storageOptions = new CosmosDbPartitionedStorageOptions()
        {
            CosmosDbEndpoint = config["CosmosDbEndpoint"],
            AuthKey = config["CosmosDbAuthKey"],
            DatabaseId = config["CosmosDbDatabaseId"],
            ContainerId = config["CosmosDbConversationStateContainerId"]
        };

        _cosmosDbTimeToLive = config.GetValue<int>("CosmosDbTimeToLive");
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        using (var client = new CosmosClient(
            _storageOptions.CosmosDbEndpoint,
            _storageOptions.AuthKey,
            _storageOptions.CosmosClientOptions ?? new CosmosClientOptions()))
        {
            // Create the contaier with the provided TTL
            var containerResponse = await client
                .GetDatabase(_storageOptions.DatabaseId)
                .DefineContainer(_storageOptions.ContainerId, "/id")
                .WithDefaultTimeToLive(_cosmosDbTimeToLive)
                .WithIndexingPolicy().WithAutomaticIndexing(false).Attach()
                .CreateIfNotExistsAsync(_storageOptions.ContainerThroughput)
                .ConfigureAwait(false);
        }
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

Startup.cs

Por fim, atualize Startup.cs para usar o inicializador de armazenamento e o Cosmos DB para estado:

// Existing code omitted...

// commented out MemoryStorage, since we are using CosmosDbPartitionedStorage instead
// services.AddSingleton<IStorage, MemoryStorage>();

// Add the Initializer as a HostedService (so it's called during the app service startup)
services.AddHostedService<CosmosDbStorageInitializerHostedService>();

// Create the storage options for User state
var userStorageOptions = new CosmosDbPartitionedStorageOptions()
{
    CosmosDbEndpoint = Configuration["CosmosDbEndpoint"],
    AuthKey = Configuration["CosmosDbAuthKey"],
    DatabaseId = Configuration["CosmosDbDatabaseId"],
    ContainerId = Configuration["CosmosDbUserStateContainerId"]
};

// Create the User state. (Used in this bot's Dialog implementation.)
services.AddSingleton(new UserState(new CosmosDbPartitionedStorage(userStorageOptions)));

// Create the storage options for Conversation state
var conversationStorageOptions = new CosmosDbPartitionedStorageOptions()
{
    CosmosDbEndpoint = Configuration["CosmosDbEndpoint"],
    AuthKey = Configuration["CosmosDbAuthKey"],
    DatabaseId = Configuration["CosmosDbDatabaseId"],
    ContainerId = Configuration["CosmosDbConversationStateContainerId"]
};

// Create the Conversation state. (Used by the Dialog system itself.)
services.AddSingleton(new ConversationState(new CosmosDbPartitionedStorage(conversationStorageOptions)));

// Existing code omitted...

Agora, o Cosmos DB excluirá automaticamente os registros de estado da conversa após 30 segundos de inatividade.

Para obter mais informações, confira Configurar a vida útil no Azure Cosmos DB

Para testar o bot

  1. Caso ainda não tenha feito isso, instale o Bot Framework Emulator.
  2. Execute o exemplo localmente em seu computador.
  3. Inicie o Emulator, conecte-se ao bot e envie uma mensagem a ele.
  4. Após uma das solicitações, aguarde 30 segundos antes de responder.