保存用户和聊天数据

适用于:SDK v4

机器人在本质上是无状态的。 部署机器人后,根据轮次的不同,它不一定会在相同的进程或计算机中运行。 但是,机器人可能需要跟踪聊天上下文,以便可以管理聊天行为并记住先前问题的回答。 使用 Bot Framework SDK 的状态和存储功能可将状态添加到机器人。 机器人使用状态管理和存储对象来管理并持久保存状态。 状态管理器提供一个抽象层,让你可以使用属性访问器来访问状态属性(不考虑基础存储的类型)。

注意

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

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

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

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

先决条件

关于此示例

收到用户输入以后,此示例会检查存储的聊天状态,看系统以前是否已提示该用户提供其名称。 如果否,则系统会请求用户的名称,并将该输入存储在用户状态中。 如果是这样,系统会使用用户状态中存储的名称与用户聊天,并将用户的输入数据以及接收时间、输入通道 ID 返回给用户。 将会从用户对话数据中检索时间和通道 ID,然后将其保存到对话状态中。 以下图示显示了机器人、用户配置文件和聊天数据类之间的关系。

定义类

设置状态管理时,第一步是定义类,这些类将包含需要在用户和聊天状态中管理的信息。 本文中使用的示例定义以下类:

  • UserProfile.cs 中,为机器人将要收集的用户信息定义 UserProfile 类。
  • ConversationData.cs 中定义一个 ConversationData 类,用于在收集用户信息时控制聊天状态。

以下代码示例显示了 UserProfileConversationData 类的定义。

UserProfile.cs

public class UserProfile
{
    public string Name { get; set; }
}

ConversationData.cs

public class ConversationData
{
    // The time-stamp of the most recent incoming message.
    public string Timestamp { get; set; }

    // The ID of the user's channel.
    public string ChannelId { get; set; }

    // Track whether we have already asked the user's name
    public bool PromptedUserForName { get; set; } = false;
}

创建聊天和用户状态对象

接下来,注册用于创建 UserStateConversationState 对象的 MemoryStorage。 用户和聊天状态对象在Startup时创建,依赖项会注入机器人构造函数中。 机器人的其他已注册服务:凭据提供程序、适配器和机器人实现。

Startup.cs

// {
//     TypeNameHandling = TypeNameHandling.All,
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state");

// With a custom JSON SERIALIZER, use this instead.
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state", jsonSerializer);

/* END AZURE BLOB STORAGE */

Bots/StateManagementBot.cs

private BotState _conversationState;
private BotState _userState;

public StateManagementBot(ConversationState conversationState, UserState userState)
{
    _conversationState = conversationState;
    _userState = userState;
}

添加状态属性访问器

现在,使用 CreateProperty 方法来创建属性访问器,该方法提供 BotState 对象的句柄。 每个状态属性访问器允许获取或设置关联状态属性的值。 在使用状态属性之前,使用每个访问器从存储加载属性,并从状态缓存获取该属性。 为了将范围设置适当的密钥与状态属性相关联,请调用 GetAsync 方法。

Bots/StateManagementBot.cs

var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));

从机器人访问状态

前面几个部分介绍了将状态属性访问器添加到机器人的初始化时步骤。 现在,可以在运行时使用这些访问器来读取和写入状态信息。 下面的代码示例使用以下逻辑流:

  • 如果 userProfile.Name 为空且 conversationData.PromptedUserForNametrue,请检索提供的用户名并将其存储在用户状态中。
  • 如果 userProfile.Name 为空且 conversationData.PromptedUserForNamefalse,请索要用户名。
  • 如果之前已存储 userProfile.Name,请从用户输入中检索消息时间和通道 ID,将所有数据回显给用户,然后将检索的数据存储在对话状态中。

Bots/StateManagementBot.cs

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    // Get the state properties from the turn context.

    var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
    var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());

    var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
    var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile());

    if (string.IsNullOrEmpty(userProfile.Name))
    {
        // First time around this is set to false, so we will prompt user for name.
        if (conversationData.PromptedUserForName)
        {
            // Set the name to what the user provided.
            userProfile.Name = turnContext.Activity.Text?.Trim();

            // Acknowledge that we got their name.
            await turnContext.SendActivityAsync($"Thanks {userProfile.Name}. To see conversation data, type anything.");

            // Reset the flag to allow the bot to go through the cycle again.
            conversationData.PromptedUserForName = false;
        }
        else
        {
            // Prompt the user for their name.
            await turnContext.SendActivityAsync($"What is your name?");

            // Set the flag to true, so we don't prompt in the next turn.
            conversationData.PromptedUserForName = true;
        }
    }
    else
    {
        // Add message details to the conversation data.
        // Convert saved Timestamp to local DateTimeOffset, then to string for display.
        var messageTimeOffset = (DateTimeOffset)turnContext.Activity.Timestamp;
        var localMessageTime = messageTimeOffset.ToLocalTime();
        conversationData.Timestamp = localMessageTime.ToString();
        conversationData.ChannelId = turnContext.Activity.ChannelId.ToString();

        // Display state data.
        await turnContext.SendActivityAsync($"{userProfile.Name} sent: {turnContext.Activity.Text}");
        await turnContext.SendActivityAsync($"Message received at: {conversationData.Timestamp}");
        await turnContext.SendActivityAsync($"Message received from: {conversationData.ChannelId}");
    }
}

在退出轮次处理程序之前,使用状态管理对象的 SaveChangesAsync() 方法将所有状态更改写回到存储中。

Bots/StateManagementBot.cs

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
    await base.OnTurnAsync(turnContext, cancellationToken);

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

测试机器人

  1. 下载并安装最新的 Bot Framework Emulator
  2. 在计算机本地运行示例。 如需说明,请参阅 C#JavaScriptJavaPython 示例的 README 文件。
  3. 使用 Emulator 测试示例机器人。

其他信息

本文介绍了如何将状态添加到机器人。 有关相关主题的详细信息,请参阅下表。

主题 备注
状态管理 所有状态管理调用都是异步的,默认采用“上次写入优先”。 在实践中,应在机器人中获取、设置和保存尽量邻近的状态。 有关如何实现乐观锁定的讨论,请参阅为机器人实现自定义存储
关键业务数据 请使用机器人状态来存储首选项、用户名或订购的最后一个项目,但不要用它来存储关键的业务数据。 对于关键数据,请创建自己的存储组件或将数据直接写入存储
识别器-文本 该示例使用 Microsoft/Recognizers-Text 库来分析和验证用户输入。 有关详细信息,请参阅概述页。

后续步骤

了解如何向用户提出一系列问题,验证他们的回答,然后保存其输入。