在上一篇([04]瞭解EchoBot的程式碼結構)看了整個EchoBot的骨架之後,相信對於整個Chatbot的撰寫有了一些基本的概念了。
接下來要做的就是進入細部看細節。看看每一個環節實際怎麽撰寫。
先從State開始,如果説Bot沒辦法記得使用者的習慣以及設定,那麽整個使用體驗會很差。舉例來説,如果沒有state,那麽每一次都要問使用者的姓名,就太笨了。
這篇來看看V4裡面的Bot State Management。
V4裡面的BotState
還記得上一篇有介紹一個Accessor的概念,這個東西是整個SDK裡面State最基本核心。
Accessor就是一個abstract,把實際儲存的方式和呼叫抽離。
要實作一個Accessor需要實作IStatePropertyAccessor
這個interface定義了一定要實作Get、Set還有Delete方法。要new這個Accessor的時候要傳入一個字串用來做儲存内容的區別。
BotState
是SDK提供的一個實作過的Accessor,這是一個abstract class,換句話説,如果有要自己刻Accessor可以用這個為Base在來寫。
在V4 SDK提供了兩個繼承BotState
的Accessor,分別叫做:
- UserState
- ConversationState
這兩個State的最大差異在於資料儲存的時效。
UserState
主要用來儲存和某個User有關的信息,例如,preference設定,姓名這些類型的資料。
ConversationState
主要用來儲存這一個Conversation的信息,例如,可以用來儲存流程資訊,或者目前這個Conversation的問題問到了那裏。
BotState實際底層儲存的位置
上面提到了BotState
是一個abstract,可是實際上資料會要儲存到某一個地方,那那些地方可以儲存呢?
只要有實作IStorage
的interface都可以作爲BotState的實際的儲存位置,在SDK裡面有支援3個地方:
- Memory - 儲存在記憶體裡面 - 開發時候測試用
- Azure Blob - 儲存在Azure上的storage
- Azure CosmosDB - Azure上面的NoSQL儲存空間
如果需要儲存在別的地方,可以看一下有沒有第三方寫好的其他Storage位置。
BotState的整個流程
上面是整個BotState的概念,那麽如果看整個的呼叫流程是怎麽樣呢?
DialogSet + DialogContext
- 可以想象成這個是SDK呼叫BotState的部分。
透過這個Sequence Diagram應該對整個流程有個更清楚的概念了,在實際對應回code之前,還有一個部分想要説一下,那就是用Accessor的好處。
Accessor的好處
Accessor除了提供一個和實體storage隔離的abstract層之外,還有提供了一些好處:
- 資料是Lazy Loaded - 換句話説當第一次實際用到資料的時候才會load進來
- 有個共用cache - 換句話説讀進來了之後下次在讀速度就更快了,并且有機制知道資料有沒有被修改需要更新cache
- 資料一起寫入 - 當呼叫儲存的時候,資料是一批進去
理論對應到實踐
上面介紹完了BotState之後,在回來看看EchoBot裡面這個部分有關的程式碼。
EchoBotAccessors.cs
首先,第一個部分是EchoBotAccessors.cs
,這邊定義了會用到的Accessor以及他的Property(字串)對應名稱:
public class EchoBotAccessors
{
...
/// <summary>
/// Gets the <see cref="IStatePropertyAccessor{T}"/> name used for the <see cref="CounterState"/> accessor.
/// </summary>
/// <remarks>Accessors require a unique name.</remarks>
/// <value>The accessor name for the counter accessor.</value>
public static string CounterStateName { get; } = $"{nameof(EchoBotAccessors)}.CounterState";
/// <summary>
/// Gets or sets the <see cref="IStatePropertyAccessor{T}"/> for CounterState.
/// </summary>
/// <value>
/// The accessor stores the turn count for the conversation.
/// </value>
public IStatePropertyAccessor<CounterState> CounterState { get; set; }
...
}
這個部分對應到了這篇一開始提到的Accessor的概念,接下來這個怎麽被整合到Framework裡面呢?
Startup.cs
切換到Startup.cs
,這邊在ConfigureServices
方法裡面做了兩件事情:
- 設定ConversationState的Storage為Memory
services.AddBot<EchoWithCounterBot>(options => { .... IStorage dataStore = new MemoryStorage(); var conversationState = new ConversationState(dataStore); options.State.Add(conversationState); .... });
- 設定Accessor為Singleton
services.AddSingleton<EchoBotAccessors>(sp => { var options = sp.GetRequiredService <IOptions<BotFrameworkOptions>>().Value; var conversationState = options.State. OfType<ConversationState>().FirstOrDefault(); var accessors = new EchoBotAccessors(conversationState) { CounterState = conversationState. CreateProperty<CounterState>( EchoBotAccessors.CounterStateName), }; return accessors; });
設定好了之後,在Bot裡面怎麽實際使用呢?
EchoWithCounterBot.cs
這個時候切換到實際的Bot程式碼:EchoWithCounterBot.cs
在OnTurnAsync
裡面:
.....
if (turnContext.Activity.Type == ActivityTypes.Message)
{
// 取得bot state
var state = await _accessors.CounterState.
GetAsync(turnContext, () => new CounterState());
// 把第幾輪的數字加一
state.TurnCount++;
// 把值存回去
await _accessors.CounterState.SetAsync(turnContext, state);
// 實際寫到Storage裡面
await _accessors.ConversationState.SaveChangesAsync(turnContext);
.....
}
....
結語
這篇從BotState的角度更詳細介紹了V4裡面對State Management的處理方式,并且重新看了EchoBot裡面相關的程式碼。
相信看完了這篇對於怎麽寫BotState應該很清楚了。
下一篇([06]使用BotState儲存使用者的相關信息)將實際來撰寫,將透過ConversationState控制信息的流程,然後UserState儲存使用者的姓名。