基于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
)
我们之前多次的讨论过,在聊天机器人应用中有如下一些逻辑的设计:
Channel
Conversation
Turn
User
andBot
, 实际上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
的子类:
ConversationState
UserState
PrivateConversationState
以上这些类是用于状态管理,同时为了状态中的数据可以持久保存,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));
注意
需要提前在构造函数里注入需要自动保存的状态对象。