Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 記錄軟體開發的點點滴滴 著重於微軟技術、網頁開發、DevOps、C#, Asp .net Mvc、Azure、AI、Chatbot、Docker、Data Science

[Bot Framework V4][05]搞懂Bot的State Management - 怎麽儲存信息

[Bot Framework V4][05]搞懂Bot的State Management - 怎麽儲存信息.jpg
圖片來源:https://pixabay.com/en/books-spine-colors-pastel-1099067/ 

在上一篇([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,分別叫做:

  1. UserState
  2. ConversationState

這兩個State的最大差異在於資料儲存的時效

UserState主要用來儲存和某個User有關的信息,例如,preference設定,姓名這些類型的資料。

ConversationState主要用來儲存這一個Conversation的信息,例如,可以用來儲存流程資訊,或者目前這個Conversation的問題問到了那裏。

在EchoBot裡面,目前回話在第幾個Turn儲存的位置是ConversationState,合理因爲這個信息是這個Conversation才有的内容。
這邊注意一下,使用V4的BotState去儲存一些preference或者user相關的信息很好,但是一些商業邏輯的資料還是自己直接儲存(例如存到Sql Server之類),而不是依賴BotState比較好。
BotState除了開發者自己呼叫以外,SDK本身也會呼叫用來記錄一些信息。

BotState實際底層儲存的位置

上面提到了BotState是一個abstract,可是實際上資料會要儲存到某一個地方,那那些地方可以儲存呢?

只要有實作IStorage的interface都可以作爲BotState的實際的儲存位置,在SDK裡面有支援3個地方:

  1. Memory - 儲存在記憶體裡面 - 開發時候測試用
  2. Azure Blob - 儲存在Azure上的storage
  3. Azure CosmosDB - Azure上面的NoSQL儲存空間

如果需要儲存在別的地方,可以看一下有沒有第三方寫好的其他Storage位置。

BotState的整個流程

上面是整個BotState的概念,那麽如果看整個的呼叫流程是怎麽樣呢?

bot-builder-dialog-state.png
整個BotState的Sequence Diagram,圖片來源:https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-dialog-state?view=azure-bot-service-4.0
這邊可以先暫時忽略DialogSet + DialogContext - 可以想象成這個是SDK呼叫BotState的部分。

透過這個Sequence Diagram應該對整個流程有個更清楚的概念了,在實際對應回code之前,還有一個部分想要説一下,那就是用Accessor的好處。

Accessor的好處

Accessor除了提供一個和實體storage隔離的abstract層之外,還有提供了一些好處:

  1. 資料是Lazy Loaded - 換句話説當第一次實際用到資料的時候才會load進來
  2. 有個共用cache - 換句話説讀進來了之後下次在讀速度就更快了,并且有機制知道資料有沒有被修改需要更新cache
  3. 資料一起寫入 - 當呼叫儲存的時候,資料是一批進去

理論對應到實踐

上面介紹完了BotState之後,在回來看看EchoBot裡面這個部分有關的程式碼。

以下程式碼和原本比有刪減一些内容,只保留和BotState有關的重點。

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儲存使用者的姓名。


如果文章對您有幫助,就請我喝杯飲料吧
街口支付QR Code
街口支付QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus