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
台灣 Pay QR Code
台灣 Pay QR Code
Line Pay 一卡通 QR Code
Line Pay 一卡通 QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus