Alan Tsai 的學習筆記


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

[chatbot + AI = 下一代操作模式][05]深入IDialogContext - 處理上下文、對外的聯係和state

[chatbot + AI = 下一代操作模式][05]深入IDialogContext - 處理上下文以及對外的聯係.jpg
圖片來源:https://pixabay.com/en/books-spine-colors-pastel-1099067/ 

在上一篇([04]瞭解BotBuilder的組成)完整看了EchoBot的程式碼組成,并且瞭解了BotBuilder一些常見的物件。并且依照所學調整了部分程式碼。

這一篇將會聚焦在其中一個管理上下文以及對來連綫的物件IDialogContext

這篇的程式碼github頁面是alantsai-samples/mhat-hotelbot:blog/chapter-05

IDialogContext的用途

還記得主要邏輯是在RootDialog.cs裡面,而其中在裏面的程式碼用IDialogContext做了兩件事情:

  1. context.Wait() - 用來控制接下來的訊息要被什麽方法處理
  2. context.PostyAsync - 用來回傳訊息給使用者的方法

透過這兩個方法,可以看出IDialogContext有兩個作用:

  1. 控制流程 (底層Internals.IDialogStack) - Wait代表接下來的訊息處理,還有一些別的,例如 Call 用來呼叫別的Dialog
  2. 和外界聯係 (底層Internals.IBotToUser) - PostAsync把訊息回傳給使用者。

IDialogContext還有一個重要的功能,就是用來儲存和conversation或者user有關訊息的方式 (底層Internals.IBotData)。

儲存和conversation或者user有關訊息的方式

記錄訊息有三個等級:

ConversationData
和某個conversation有關的記錄。
PrivateConversationData
和某個user在某個conversation有關的記錄
UserData
和某個user有關的訊息 - 包含所有的channel以及conversation。

預設,這些訊息是儲存在Memory裡面,因此上了Production等級記得要做一些調整,預設有支援可以儲存在Azure的Cosmos DB或者Table Storage,當然如果有需要也可以儲存在別的地方,可以自己寫或者找套件。

這3個等級要依照需求去儲存,例如,假設是一個和使用者有關的訊息但是不和任一個conversation有關,例如他的名字,那麽可以儲存在UserData。反過來説,如果是某些訊息和某次conversation有關,例如 查旅館的時候記錄選擇了那個,那麽就可以使用PrivateConversationData

最後,要使用這些儲存很容易,把他們當成Dictionary使用就好。

Conversation是什麽

在上面提到儲存等級的時候,提到了兩個詞,Channel以及conversation。

channel之前提到過,其實就是使用者使用的溝通平臺。有可能你的bot部署到多個平臺,那麽用channel來區分可以做一些特定平臺相關處理

conversation則是某一次的交談。例如,一整串的對話就屬於一組conversation。等到10天後又過來就變成另外一組conversation。

這些資料其實有儲存在Activity裡面,我們透過emulator可以看到實際的值:

Bot Framework Emulator_2018-07-08_18-18-23.png
第1點可以看到是channel的值,然後conversation id來區分conversation

實際修改

上面提到了這些理論,接下來我們就來實際調整chatbot。

這邊的需求是,如果沒有使用者的姓名,需要先和使用者取得,取到了之後,不管使用者輸入什麽都會直接回傳并且加上使用者的姓名。

要做到上面的需求,整個流程變成:

  1. 先判斷是否已經有使用者的姓名
  2. 當詢問使用者名稱的時候記錄下一個回答是記錄名字
  3. 把回傳是使用者名稱,儲存起來

先判斷是否已經有使用者的姓名

這邊使用了UserData做儲存,因爲使用者姓名可能在多個conversation都可以使用(當然用什麽取決需求)

整個code調整如下:

private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
	var activity = await result as Activity;

	context.UserData.TryGetValue<string>("Name", out string name);

	if(string.IsNullOrEmpty(name))
	{
		// 還沒有姓名
	}
	else
	{
		// 已經有姓名直接輸出 姓名 + 輸入内容
		await context.PostAsync($"{name}: {activity.Text}");
	}

	context.Wait(MessageReceivedAsync);
}

當詢問使用者名稱的時候記錄下一個回答是記錄名字

我們接下來要處理詢問使用者名稱的部分。整個code如下:

....
if(string.IsNullOrEmpty(name))
{
	context.PrivateConversationData.
		TryGetValue<bool>("IsAskName", out bool isAskName);

	if(isAskName)
	{
		// 詢問過名字,準備記錄
	}
	else
	{
		context.PrivateConversationData.SetValue<bool>("IsAskName", true);

		await context.PostAsync("您的名字是?");
	}
}
....

PrivateConversationData來記錄是否詢問過名字,這邊選擇PrivateConversationData是因爲這個資訊和使用者有關,并且只和這次的conversation有關。

需要這個中繼的IsAskName是這樣才好區分已經在問名字還是正在詢問名字。

後面有更加好的處理方式,現在先暫時這樣。

把回傳是使用者名稱,儲存起來

那最後一塊就是做名稱儲存就是這樣:

...
if(isAskName)
{
	context.UserData.SetValue<string>("Name", activity.Text);

	await context.PostAsync($"{activity.Text} 您好,能夠幫助您什麽");
}
else
{
	context.PrivateConversationData.SetValue<bool>("IsAskName", true);

	await context.PostAsync("您的名字是?");
}
...

最後結果

接下來我們可以把chatbot run起來并且用emulator做一些測試。

Bot Framework Emulator_2018-07-08_18-42-42.png
測試結果
這邊會發現有個地方滿違和,也就是我需要先輸入一個你好才觸發整個chatbot。這個其實可以在使用者剛加入chatbot的時候詢問。這個就可以用ActivityTypes.ConversationUpdate來做這些處理。

結語

這一篇介紹了IDialogContext這個物件的作用,并且這篇著重於如何儲存個人資料。希望透過這篇對於IDialogContext的作用及用途會更清楚。

在下一篇([06]不只能輸出文字 - 看看各種内建卡片模式以及可自定的Adaptive Card)我們切換一下,如果只能輸出一般文字對現在使用者來説還是太乾了,來看看BotbBuilder在傳輸不同資料格式上面有什麽幫助。

comments powered by Disqus