Partilhar via


Expirar uma conversa

APLICA-SE A: SDK v4

Um bot às vezes 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:

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

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.

Para a criação de novos bots, considere usar o Microsoft Copilot Studio e leia sobre como escolher a solução de copilot certa.

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

Pré-requisitos

Sobre este exemplo

O código de exemplo neste artigo começa com a estrutura de um bot de várias voltas e estende a funcionalidade desse 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 expirante é realizado adicionando uma propriedade de hora do último acesso ao estado de conversa do bot. Esse valor de propriedade é então comparado com a hora atual dentro do manipulador de atividade antes das atividades de processamento.

Nota

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

appsettings.json

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

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

Bots\DialogBot.cs

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

Em vez de criar o acessador da propriedade de estado da caixa de diálogo embutido no método, você está criando e gravando-o no momento da OnMessageActivityAsync inicialização. O bot precisará do acessador da propriedade state 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;

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

Finalmente, adicione código ao método do OnTurnAsync bot para limpar o estado da caixa de 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 tempo de vida (TTL) 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 de contêiner (usando os SDKs do Cosmos DB específicos do idioma).

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

Comece com uma nova cópia do exemplo de prompt de várias voltas e adicione o Microsoft.Bot.Builder.Azure pacote NuGet 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. O TTL padrão é definido no ConversationState contêiner, mas não no UserState.

CosmosDbStorageInitializerHostedService.cs

Em seguida, crie uma CosmosDbStorageInitializerHostedService classe, que criará o contêiner com o Time To Live 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, atualize Startup.cs 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'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...

O Cosmos DB agora 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 você ainda não fez isso, instale o Bot Framework Emulator.
  2. Execute a amostra localmente na sua máquina.
  3. Inicie o emulador, conecte-se ao seu bot e envie uma mensagem para ele.
  4. Após um dos prompts, aguarde 30 segundos antes de responder.