Expirar uma conversa

aplica-se a: SDK v4

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

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

Pré-requisitos

Sobre este exemplo

O código de exemplo neste artigo começa com a estrutura de um bot de vários turnos e estende a funcionalidade do bot adicionando código adicional (fornecido nas seções a seguir). Esse código estendido demonstra como limpar o estado da conversa após um determinado período de tempo.

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

Esse tipo de conversa de expiração é realizado adicionando uma propriedade de hora acessada pela última vez ao estado da conversa do bot. Esse valor da propriedade é comparado com a hora atual dentro do manipulador de atividade antes de processar atividades.

Observação

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

appsettings.json

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

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

Bots\DialogBot.cs

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

Observe que, em vez de criar o acessador de propriedade de estado da caixa de diálogo em linha no método , você está criando e gravando-o OnMessageActivityAsync no momento da inicialização. O bot precisará do acessador de propriedade de estado não apenas para executar a caixa de diálogo, mas também para limpar o estado da caixa de 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;

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

Por fim, adicione código ao método do bot para limpar o estado da caixa de OnTurnAsync 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

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

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

Comece com uma nova cópia do exemplo de prompt de vários turnos e adicione o pacote Microsoft.Bot.Builder.Azure NuGet ao projeto.

appsettings.json

Atualize appsettings.jspara incluir Cosmos DB de armazenamento:

{
  "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 as duas ContainerIds, uma para UserState e outra para ConversationState . Isso é porque estamos definindo um tempo de vida padrão no ConversationState contêiner, mas não em UserState .

CosmosDbStorageInitializerHostedService.cs

Em seguida, crie CosmosDbStorageInitializerHostedService uma classe, que criará o contêiner com o Tempo De Vida Real configurado.

// 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, Startup.cs atualize para usar o inicializador de armazenamento e o Cosmos Db para o 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 is 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...

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

Para obter mais informações, consulte Configurar o tempo de vida no Azure Cosmos DB

Para testar o bot

  1. Se ainda não tiver feito isso, instale o Bot Framework Emulator.
  2. Execute o exemplo localmente em seu computador.
  3. Inicie o Emulador, conecte-se ao bot e envie uma mensagem para ele.
  4. Após um dos prompts, aguarde 30 segundos antes de responder.