管理状态
适用于: 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 包含其中的三个“桶”:
- 用户状态
- 聊天状态
- 私人聊天状态
所有这些桶都是 bot state 类的子类,可以派生该类来定义具有不同范围的其他类型的桶。
这些预定义的桶已限定到特定的可见范围内,具体取决于桶:
- 不管聊天内容如何,机器人在该通道中与该用户展开的任何聊天轮次都会提供用户状态
- 对话状态可在特定对话的任何回合中使用,与用户无关,例如在群组对话中
- 私人聊天状态的范围限定为特定聊天和特定用户
提示
用户状态和聊天状态的范围根据通道进行限定。 使用不同的通道访问机器人的同一个人显示为不同的用户,每个通道有一个用户,并且每个用户具有不同的用户状态。
用于其中每个预定义桶的键特定于用户和/或聊天。 在设置状态属性值时,会在内部定义关键字,并在回合上下文中包含相关信息,以确保每个用户或对话都被放置在正确的存储桶和属性中。 具体而言,将按如下所述定义键:
- 用户状态使用通道 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
何时使用每种类型的状态
聊天状态非常适合用于跟踪聊天的上下文,例如:
- 机器人是否向用户提出了问题,问题是什么
- 聊天的当前主题或最后一个主题是什么
用户状态非常适合用于跟踪有关用户的信息,例如:
- 非关键性用户信息,例如姓名和首选项、警报设置或警报首选项
- 他们与机器人最后一次对话的信息
- 例如,产品支持机器人可以跟踪用户咨询过的产品。
私人聊天状态非常适合用于支持群组聊天的通道,但在其中需要同时跟踪用户和聊天特定的信息。 例如,如果你有一个课堂抢答机器人:
- 该机器人可以聚合并显示学生对给定问题的回答。
- 该机器人可以聚合每位学生的成绩,并在会话结束时,以私密方式将该信息中继回到相应的学生。
有关使用这些预定义桶的详细信息,请参阅状态操作方法文章。
连接到多个数据库
如果机器人需要连接到多个数据库,请为每个数据库创建一个存储层。 如果机器人收集的信息有不同的安全性、并发性或数据位置需求,则可以选择使用多个数据库。
对于每个存储层,创建支持状态属性所需的状态管理对象。
状态属性访问器
状态属性访问器用于实际读取或写入某个状态属性,并提供 get、set 和 delete 方法用于从轮次内部访问状态属性。 若要创建访问器,必须提供属性名称(通常是在初始化机器人时提供)。 然后,可以使用该访问器来获取和处理机器人状态的该属性。
访问器允许 SDK 从基础存储获取状态并更新机器人的状态缓存。 状态缓存是机器人维护的本地缓存,用于存储状态对象,并允许在不访问基础存储的情况下执行读取和写入操作。 如果状态尚未进入缓存,则调用访问器的 get 方法可以检索状态并将其放入缓存。 检索后,可以像处理本地变量一样处理状态属性。
访问器的 delete 方法会从缓存和基础存储中删除属性。
重要
首次调用访问器的 get 方法时,必须提供一个工厂方法用于创建对象(如果状态中尚不存在该对象)。 如果没有给出工厂方法,就会出现异常。 在状态操作方法文章中可以找到有关如何使用工厂方法的详细信息。
若要保存对你从访问器获取的状态属性所做的任何更改,必须更新状态缓存中的属性。 为此,可以调用访问器的 set 方法,以便设置缓存中属性的值;如果以后需要在该轮次中读取或更新该属性,也可以使用此方法。 若要将该数据实际保存到基础存储(从而使该数据在当前轮次结束后可供使用),必须保存状态。
状态属性访问器方法的工作原理
访问器方法是机器人与状态交互的主要方法。 下面介绍了每个方法的工作原理以及基础层的交互方式:
- 访问器的 get 方法:
- 访问器从状态缓存请求属性。
- 如果该属性在缓存中,则返回它。 否则,从状态管理对象获取该属性。 (如果尚未处于状态,请使用访问器 get 调用中提供的工厂方法。)
- 访问器的 set 方法:
- 使用新属性值更新状态缓存。
- 状态管理对象的 save changes 方法:
- 检查对状态缓存中属性所做的更改。
- 将属性写入存储。
对话中的状态
对话库使用定义在机器人对话状态上的对话状态属性访问器来保留对话在对话中的位置。 对话状态属性还允许每个对话在回合之间存储暂时性信息。
自适应对话具有更复杂的内存范围结构,可以更方便地访问配置和识别结果等。 对话管理器使用用户和对话状态管理对象来提供这些内存范围。
有关对话库的信息,请参阅对话库一文。
- 有关组件对话和瀑布对话的具体信息,请参阅组件对话和瀑布对话。
- 有关自适应对话的具体信息,请参阅自适应对话简介和 在自适应对话中管理状态。
保存状态
调用访问器的 set 方法来记录更新的状态时,该状态属性尚未保存到持久性存储,而只是保存到了机器人的状态缓存。 若要将状态缓存的任何更改保存到持久性状态,必须调用状态管理对象的 save changes 方法。可以针对上述机器人状态类的实现(例如用户状态或聊天状态)使用此方法。
调用状态管理对象(如上文提到的水桶)的保存更改方法,会保存状态缓存中的所有属性,这些属性是为该存储桶设置的,但不会保存在机器人状态中的任何其他存储桶中。
提示
机器人状态实现“最后一次写入优先”行为,即最后一次写入会改写上一次写入的状态。 此行可能适合许多应用场合,但也会带来负面影响,尤其是横向扩展方案中需要提供某种并发度或控制延迟时。
如果某些定制中间件在轮次处理程序完成后可能需要更新状态,请考虑在中间件中处理状态。