Expiración de una conversación

SE APLICA A: SDK v4

A veces, un bot necesita reiniciar una conversación desde el principio. Por ejemplo, si un usuario no responde después de un período de tiempo determinado. En este artículo se describen dos métodos para expirar una conversación:

  • Realice un seguimiento de la última vez que se recibió un mensaje de un usuario y borre el estado si la hora es mayor que una longitud preconfigurada al recibir el siguiente mensaje del usuario. Para más información, consulte la sección de expiración de la interacción del usuario.
  • Use una característica de capa de almacenamiento, como el período de vida (TTL) de Cosmos DB, para borrar automáticamente el estado después de un período de tiempo preconfigurado. Para más información, consulte la sección de expiración de almacenamiento.

Nota:

Los SDK de JavaScript, C# y Python de Bot Framework seguirán siendo compatibles, pero el SDK de Java se va a retirar con la compatibilidad final a largo plazo que finaliza en noviembre de 2023.

Los bots existentes creados con el SDK de Java seguirán funcionando.

Para la creación de nuevos bots, considera el uso de Power Virtual Agents y lee sobre cómo elegir la solución de bot de chat adecuada.

Para obtener más información, consulta El futuro de la creación de bots.

Requisitos previos

Acerca de este ejemplo

El código de ejemplo de este artículo comienza con la estructura de un bot de varios turnos, y amplía la funcionalidad de ese bot añadiendo código adicional (proporcionado en las siguientes secciones). Este código extendido muestra cómo borrar el estado de la conversación después de que haya transcurrido un período de tiempo determinado.

Expiración de la interacción del usuario

Este tipo de conversación de expiración se logra agregando una propiedad de hora de último acceso al estado de conversación del bot. A continuación, este valor de propiedad se compara con la hora actual dentro del controlador de actividad antes de procesar las actividades.

Nota:

En este ejemplo se usa un tiempo de espera de 30 segundos para facilitar la prueba de este patrón.

appsettings.json

En primer lugar, agregue una configuración ExpireAfterSeconds a appsettings.json:

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

Bots\DialogBot.cs

A continuación, agregue los campos ExpireAfterSeconds, LastAccessedTimeProperty y DialogStateProperty a la clase bot e inicialícelos en el constructor del bot. Agregue también un parámetro IConfiguration al constructor con el que se va a recuperar el valor ExpireAfterSeconds.

En lugar de crear el descriptor de acceso de la propiedad de estado del cuadro de diálogo insertado en el método OnMessageActivityAsync, va a crearlo y registrarlo en el momento de la inicialización. El bot necesitará el descriptor de acceso de propiedad de estado no solo para ejecutar el cuadro de diálogo, sino también para borrar el estado del 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 último, agregue código al método OnTurnAsync del bot para borrar el estado del diálogo si la conversación es demasiado antigua.

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);
}

Expiración de almacenamiento

Cosmos DB proporciona una función de tiempo de vida (TTL) que le permite eliminar elementos automáticamente de un contenedor después de un determinado período de tiempo. Esto se puede configurar desde Azure Portal o durante la creación del contenedor (mediante los SDK de Cosmos DB específicos del lenguaje).

El SDK de Bot Framework no expone una configuración de TTL. Sin embargo, la inicialización del contenedor se puede invalidar y el SDK de Cosmos DB se puede usar para configurar TTL antes de la inicialización del almacenamiento de Bot Framework.

Comience con una copia nueva del ejemplo de aviso de varios turnos y agregue el paquete NuGet Microsoft.Bot.Builder.Azure al proyecto.

appsettings.json

Actualice appsettings.json para incluir las opciones de almacenamiento de 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 los dos ContainerIds, uno para UserState y otro para ConversationState. El TTL predeterminado se establece en el contenedor ConversationState, pero no en UserState.

CosmosDbStorageInitializerHostedService.cs

A continuación, cree una clase CosmosDbStorageInitializerHostedService, que creará el contenedor con el período de vida 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 último, actualice Startup.cs para usar el inicializador de almacenamiento y Cosmos DB para el 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...

Cosmos DB eliminará automáticamente los registros de estado de conversación después de 30 segundos de inactividad.

Para obtener más información, consulte Configurar el tiempo de vida en Azure Cosmos DB

Prueba del bot

  1. Si aún no lo ha hecho, instale Bot Framework Emulator.
  2. Ejecute el ejemplo localmente en la máquina.
  3. Inicie Emulator, conéctese al bot y envíele un mensaje.
  4. Después de una de las solicitudes, espere 30 segundos antes de responder.