你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

管理状态

适用于: SDK v4

机器人中的状态遵循与新式 Web 应用程序相同的模式,Bot Framework SDK 提供一些抽象用于简化状态管理。

与 Web 应用一样,机器人在本质上也是无状态的;机器人的不同实例可以处理任意给定的聊天轮次。 对于某些机器人,这种简单性是首选的—机器人可以运行而不使用其他信息,或者保证所需的信息位于传入消息中。 对于其他用户来说,机器人需要保持一种状态,例如对话的继续或关于用户的现有数据,以便进行有意义的交流。

为什么我需要状态?

通过维护状态,机器人可以通过记住有关用户或对话的某些内容来拥有更有意义的对话。 例如,如果你之前曾与某个用户交谈过,则可以保存以前有关用户的信息,这样就不必再次请求它。 状态还会将数据保留时间超过当前轮次,以便机器人在多轮次对话过程中保留信息。

与机器人有关时,可以使用状态的几个层:存储层、状态管理(如下图所示的机器人状态)和状态属性访问器。 此图说明了这些层之间的交互序列的各个部分,其中实心箭头表示方法调用,以及表示响应的虚线箭头(带或不返回值)。

说明状态如何加载、缓存和存储每个轮次的序列图。

下图的流程在以下各节中进行了说明,其中每个层都有详细信息。

存储层

从实际存储状态信息的后端开始,是 存储层。 这可以视为你的物理存储,例如内存存储、Azure 或第三方服务器中的存储。

Bot Framework SDK 包括存储层的一些实现:

  • 内存存储 实现内存中存储以进行测试。 内存中数据存储仅用于本地测试,因为此存储是易失性和临时性的。 每次重启机器人时都会清除数据。
  • Azure Blob 存储 连接到 Azure Blob 存储对象数据库。
  • Azure Cosmos DB 分区存储 连接到分区的 Cosmos DB NoSQL 数据库。

重要

Cosmos DB 存储类已弃用。 最初使用 CosmosDbStorage 创建的容器没有分区键集,并且被授予默认分区键“/_partitionKey”。

通过“Cosmos DB 存储”创建的容器可以与“Cosmos DB 分区存储”一起使用。 有关详细信息,请阅读 Azure Cosmos DB 中的分区

另请注意,与旧版 Cosmos DB 存储不同,Cosmos DB 分区存储不会自动在 Cosmos DB 帐户中创建数据库。 你需要手动创建新数据库,但需要跳过手动创建容器步骤,因为 CosmosDbPartitionedStorage 存储会为你创建容器。

有关如何连接到其他存储选项的说明,请参阅 直接写入存储

状态管理

状态管理 自动将机器人状态的读取和写入到基础存储层。 状态存储为 状态属性,这些属性实际上是机器人可以通过状态管理对象读取和写入的键值对,而无需担心特定的基础实现。 这些状态属性定义信息的存储方式。 例如,检索定义为特定类或对象的属性时,你知道该数据的结构。

这些状态属性被合并到作用域的“组”中,这些组只是用来帮助组织这些属性的集合。 SDK 包括其中三个“bucket”:

  • 用户状态
  • 对话状态
  • 专用聊天状态

所有这些存储桶都是 机器人状态 类的子类,可以派生这些类来定义具有不同作用域的其他类型的存储桶。

这些预定义的存储桶的范围限定为特定可见性,具体取决于存储桶:

  • 无论对话内容如何,在机器人与该通道上的用户进行互动的任何回合中,用户状态都是可用的。
  • 会话状态在任何轮次特定对话中都可用,而不考虑用户,例如在组对话中
  • 专用聊天状态的范围包括特定聊天和该特定用户

小窍门

用户和聊天状态均按通道进行限定。 使用不同通道访问机器人的同一个人显示为不同的用户,每个通道各有一个,每个通道都有不同的用户状态。

用于每个预定义存储桶的键特定于用户和对话,或两者皆有。 在设置状态属性的值时,关键密钥会在系统内部为您定义,并且包含在轮次上下文中的信息,以确保每位用户或每次对话都被正确地分配到对应的分类空间和属性中。 具体而言,密钥的定义如下:

  • 用户状态使用 通道 ID来源 ID 创建密钥。 例如 ,{Activity.ChannelId}/users/{Activity.From.Id}#YourPropertyName
  • 会话状态使用 通道 ID会话 ID 创建密钥。 例如 {Activity.ChannelId}/conversations/{Activity.Conversation.Id}#YourPropertyName
  • 私密会话状态使用 通道 ID来源 ID会话 ID 创建密钥。 例如{Activity.ChannelId}/conversations/{Activity.Conversation.Id}/users/{Activity.From.Id}#YourPropertyName

何时使用每种状态类型

聊天状态非常适合跟踪聊天的上下文,例如:

  • 机器人是否向用户询问了一个问题,以及哪一个问题
  • 对话的当前主题是什么,或者最后一个主题是什么

用户状态非常适合跟踪有关用户的信息,例如:

  • 非关键用户信息,例如名称和首选项、警报设置或警报首选项
  • 有关他们与机器人的最后一次对话的信息
    • 例如,产品支持机器人可能会跟踪用户询问的产品。

私密会话状态适用于支持组对话的频道,在这种情况下,您希望跟踪用户和会话的特定信息。 例如,如果你有教室点击器机器人:

  • 机器人可以聚合和显示给定问题的学生响应。
  • 机器人可以汇总每个学生的表现,并在会话结束时私下反馈给他们。

有关使用这些预定义存储桶的详细信息,请参阅 状态操作指南文章

连接到多个数据库

如果机器人需要连接到多个数据库,请为每个数据库创建存储层。 如果机器人收集了具有不同安全性、并发性或数据位置需求的信息,则可以选择使用多个数据库。

对于每个存储层,请创建支持状态属性所需的状态管理对象。

State属性访问器

状态属性访问器 用于实际读取或写入您的状态属性之一,并提供 获取设置删除 方法,以便在一次回合内访问您的状态属性。 要创建访问器,必须提供属性名称,这通常是在初始化机器人时进行的。 然后,您可以使用该访问器来获取和操作机器人状态的该属性。

访问器允许 SDK 从基础存储获取状态,并为您更新机器人的状态缓存。 状态缓存是由机器人维护的本地缓存,用于存储状态对象,从而允许读取和写入作,而无需访问基础存储。 如果缓存中尚不存在,则调用访问器的 get 方法将检索状态,并将它置于缓存中。 检索后可以像局部变量一样操作状态属性。

访问器的 delete 方法从缓存中删除属性,并从基础存储中删除该属性。

重要

如果在您的状态中对象尚不存在,首次调用访问器的 get 方法时,必须提供一个工厂方法来创建该对象。 如果未提供工厂方法,则会出现异常。 有关如何使用工厂方法的详细信息,请参阅 状态作说明文章

若要保留对从访问器获取的状态属性所做的任何更改,必须更新状态缓存中的属性。 可以通过调用访问器设置方法实现此操作,该方法设置缓存中的属性值,并且如果需要可以在稍后的轮次读取或更新该属性。 若要实际将该数据保存到基础存储(因此在当前轮次后可用),必须 保存状态信息

状态属性访问器方法的工作原理

访问器方法是您的程序机器人与状态交互的主要方式。 每个工作原理以及基础层的交互方式如下所示:

  • 访问器的 get 方法:
    • 访问器从状态缓存请求属性。
    • 如果该属性位于缓存中,则返回该属性。 否则,请从状态管理对象获取它。 (如果尚未处于状态,请使用访问器中提供的工厂方法 获取 调用。
  • 访问器的 set 方法:
    • 使用新的属性值更新状态缓存。
  • 状态管理对象的 保存更改 方法:
    • 检查状态缓存中属性的更改。
    • 将此属性写入存储。

对话中的状态

对话库使用在机器人聊天状态上定义的对话状态属性访问器来保留对话中的位置。 对话状态属性还允许每个对话在轮次之间存储暂时性信息。

自适应对话具有更详细的内存范围结构,因此可以更轻松地访问配置和识别结果,等等。 对话管理器使用用户和聊天状态管理对象来提供这些内存范围。

有关对话库的信息,请参阅 对话库 文章。

保存状态

调用访问器的 set 方法以记录更新的状态时,该状态属性尚未保存到持久存储中,而是仅保存到机器人的状态缓存中。 若要将状态缓存中的任何更改保存到持久状态,必须调用状态管理对象的 保存更改 方法,该方法可用于上述机器人状态类的实现(例如用户状态或聊天状态)。

为状态管理对象(如上述存储桶)调用保存更改方法会将该存储桶当前已设置的所有属性保存在状态缓存中,但不会保存机器人状态中可能存在的任何其他存储桶的属性。

小窍门

机器人状态实现“最后写入获胜”行为,其中最后一次写入将标记之前写入的状态。 这可能适用于许多应用程序,但有影响,尤其是在横向扩展方案中,其中可能存在某种级别的并发或延迟。

如果某些自定义中间件可能在轮次处理程序完成后更新状态,请考虑 在中间件中处理状态

其他资源