使对话失效

适用于:SDK v4

机器人有时需要从头开始重新聊天。 例如,在用户有特定的一段时间未响应时。 本文介绍使聊天过期的两种方法:

  • 跟踪上次从用户收到消息的时间,如果该时间大于预配置的时长,则在收到用户的下一条消息时清除状态。 有关详细信息,请参阅用户交互过期部分。
  • 使用存储层功能(例如 Cosmos DB 生存时间 (TTL))在预配置的时长后自动清除状态。 有关详细信息,请参阅存储过期部分。

注意

Bot Framework JavaScript、C# 和 Python SDK 将继续受支持,但 Java SDK 即将停用,最终长期支持将于 2023 年 11 月结束。

使用 Java SDK 构建的现有机器人将继续正常运行。

要生成新的机器人,请考虑使用 Microsoft Copilot Studio 并阅读选择正确的助理解决方案

有关详细信息,请参阅机器人构建的未来

先决条件

关于此示例

本文中的示例代码首先演示多轮次机器人的结构,然后通过添加更多的代码(在后面的部分中会提供)来扩展该机器人的功能。 此扩展代码演示如何在特定的时间段过后清除聊天状态。

用户交互过期

要实现这种类型的聊天过期,可以将上次访问时间属性添加到机器人的聊天状态。 然后可以在处理活动之前,将此属性值与活动处理程序中的当前时间进行比较。

注意

此示例使用 30 秒超时来轻松测试此模式。

appsettings.json

首先将 ExpireAfterSeconds 设置添加到 appsettings.json:

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

Bots\DialogBot.cs

接下来,将 ExpireAfterSecondsLastAccessedTimePropertyDialogStateProperty 字段添加到 bot 类,并在机器人的构造函数中初始化这些字段。 另外,将 IConfiguration 参数添加到用于检索 ExpireAfterSeconds 值的构造函数。

此操作不是在 OnMessageActivityAsync 方法中内联创建对话状态属性访问器,而是在初始化时创建并记录该访问器。 机器人不仅需要使用状态属性访问器来运行对话,而且还需要使用它来清除对话状态。

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

最后,将代码添加到机器人的 OnTurnAsync 方法,以便在聊天太旧时清除对话状态。

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

存储过期

Cosmos DB 提供生存时间 (TTL) 功能,用于在特定的时间段过后自动从容器中删除项。 可以在 Azure 门户中或者在创建容器期间(使用特定于语言的 Cosmos DB SDK)配置 TTL。

Bot Framework SDK 不公开 TTL 配置设置。 但可以替代容器初始化,并可以使用 Cosmos DB SDK 在 Bot Framework 存储初始化之前配置 TTL。

从“多轮次提示”示例的全新副本着手,将 Microsoft.Bot.Builder.Azure NuGet 包添加到项目。

appsettings.json

更新 appsettings.json 以包含 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>"
}

请注意两个 ContainerId,一个用于 UserState,另一个用于 ConversationState。 默认的 TTL 在 ConversationState 容器上设置,但不在 UserState 上设置。

CosmosDbStorageInitializerHostedService.cs

接下来创建一个 CosmosDbStorageInitializerHostedService 类,该类将使用配置的生存时间创建容器。

// 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

最后,更新 Startup.cs 以使用存储初始化表达式,并使用 Cosmos DB 作为状态:

// 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...

现在,在处于非活动状态 30 秒后,Cosmos DB 将自动删除聊天状态记录。

有关详细信息,请参阅在 Azure Cosmos DB 中配置生存时间

测试机器人

  1. 安装 Bot Framework Emulator(如果尚未安装)。
  2. 在计算机本地运行示例。
  3. 启动 Emulator,连接到机器人并向其发送消息。
  4. 出现一条提示后,等待 30 秒后再做出响应。