基于Azure Bot Framework SDK开发的聊天机器人如何管理状态
分类: Azure机器人 ◆ 标签: #Azure Bot Framework SDK #Azure Bot Service #机器人 ◆ 发布于: 2023-08-07 22:52:46
我们这一节来学习如何管理状态,开始之前建议先了解如下的内容:
- https://www.azuredeveloper.cn/article-develop-chat-bot-by-azure-bot-service
- https://www.azuredeveloper.cn/article-hello-bot
- https://www.azuredeveloper.cn/article-create-a-full-function-chat-bot-project
- https://www.azuredeveloper.cn/article-the-message-and-activity-in-chatbot
另外本站的实例代码是构建在文章: https://www.azuredeveloper.cn/article-create-a-full-function-chat-bot-project 中已经建立好的项目之上,如果要练习本章的代码,请先使用本章内容创建一个项目。
我们之前已经讨论过了Azure Bot Framework SDK的应用是构建于Web API的应用之上,同样也是基于Asp.net Core的框架,这样带来一个问题就是每一个Bot应用实际上是无状态的应用,因为HTTP协议天然就是无状态,为了在Web应用上实现状态的管理,每个开发的框架都有实现自己的会话管理机制(Session), 大多数Web应用会基于Cookie或者是Header或者是查询字符串在每次请求的时候带上唯一的id, 用这个ID来标识每一个会话。基于这样的构想,Azure Bot Framework SDK也同样使用了类似的原理来管理状态,和Web应用不同,Bot的状态管理因为场景的差异,加入了更多的设计。
Bot应用中状态范围(State Scope)
我们之前多次的讨论过,在聊天机器人应用中有如下一些逻辑的设计:
ChannelConversationTurnUserandBot, 实际上User和Bot在某些场景下,都是User
在Bot应用预先为状态设计了三个应用范围(Scope):
- 基于
Conversation的应用范围 - 基于
User数据的应用范围 - 基于
Private Conversation的应用范围。
所谓的应用范围实际也就是数据在哪些地方可以存取,哪些场景下可以一直保存。
当用户使用某个channel连接上Bot应用之后,这个用户就有了一批表示这个场景的数据:用户的ID, Conversation的Id, Channel的Id, Turn的ID, 还有Activity的Id, 关于Turn, Activity, Channel, 以及Conversation这些概念,可以翻一下之前的文章,我们有详细介绍微软基于聊天机器人的详细设计。
当用户拥有了这些数据之后,自然就需要创建一些数据使用的范围,例如用户自己的数据:用户名,性别等等这些数据,只要在用户ID是一致的情况下,那么这些数据应该就是一致的,再比如在某一个Conversation里,只要Conversation Id是一致的,那么我们就应该认为这个范围下的数据都是同一个Conversation的,这就是状态范围的概念。
Bot的应用也确实是根据这些场景来使用数据的:
UserState: 即是用户数据的范围: 只要一个用户的用户Id和他使用的Channel的ID不变,那么这个范畴就表示是用户数据的范围, 也即UserState, 同一个用户在使用同一个Channel的情况下,开启多个Conversation, 但是在这些Conversation里访问存储在UserState状态里的数据是一致的。Conversation: 即会话的数据范围,只要一个Conversation的Id不变,那么就是表示数据在同一个Conversation。PrivateConversation: 这个使用的场景,例如在聊天室里,可能存在私人会话的场景。它的唯一要素是:Channel Id,Conversation id,User Id, 三者一致,则在同一个数据存取范围。
Bot中状态管理的组件
为了实现Bot中的状态管理,SDK提供一个基类: BotState, 同时根据预定义的三个状态范围,定义了三个基于BotState的子类:
ConversationStateUserStatePrivateConversationState
以上这些类是用于状态管理,同时为了状态中的数据可以持久保存,SDK有提供存储的基础接口和默认实现:
IStore: 该接口是用于表示存储的接口。MemoryStorage:SDK的默认实现,数据保存在内存中。AzureStorage:SDK基于Azure Storage的实现。
状态管理组件的使用方法
为了使用状态管理组件,首先需要在DI中注入服务:
services.addSingleton<IStore, MemoryStorage> services.addSingleton<UserState>() services.addSingleton<Conversation>()
在服务里注册好这些服务了之后,如果需要在Bot类里引用,我们需要从构造函数中注入:
private readonly BotState _conversationState; private readonly BotState _userState; public TestBot(ConversationState conversationState, UserState userState) { _userState = userState; _conversationState = conversationState; }
在构造函数里注入之后,如果我们需要使用不同的状态,我们需要属性存取器:
var conversationPropertyAccessor = _conversationState.CreateProperty<string>("mydatakey1"); var userName = await conversationPropetyAccessor.getAsync(turnContext, () => new string("myusername")); var userPropertyAccessor = _userState.CreateProperty<string>("mydatakey2"); var password = await userPropertyAccessor.getAsync(turnContext, ()=> new string("mypassword"))
上述例子我们创建了Conversation状态和User状态的存取器,存储了里面存放的对象。
注意
我们上面虽然实现了在不同会话里存储了状态,但是这些状态仅仅是放置在缓存里,如果需要将这些状态写回到Storage里,我们必须调用状态管理的SaveChangeAsync, 需要考虑的时是什么时候来保存状态。
持久会话数据
我们有两种方式来持久化数据:
Bot类的OnTurnAsync方法中保存。- 使用
Adapter的中间件自动保存会话数据。
我们打开Bot的定义类,添加如下的方法:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { await base.OnTurnAsync(turnContext, cancellationToken); await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); await _userState.SaveChangesAsync(turnContext, false, cancellationToken); }
这样就可以持久化会话数据了。需要注意的是调用顺序:
必须在await base.OnTurnAsync(turnContext, cancellationToken);之后调用会话保存数据。
如果是需要在Adapter类中使用中间件,打开文件Adapter\AdapterWithErrorHandler.cs在构造函数结束之前添加一行:
Use(new AutoSaveStateMiddleware(ConversationState, UserState));
注意
需要提前在构造函数里注入需要自动保存的状态对象。