在上一篇完整看了EchoBot的程式碼組成,并且瞭解了BotBuilder一些常見的物件。并且依照所學調整了部分程式碼。
這一篇將會聚焦在其中一個管理上下文以及對來連綫的物件IDialogContext。
這篇的程式碼github頁面是alantsai-samples/mhat-hotelbot:blog/chapter-05
還記得主要邏輯是在RootDialog.cs裡面,而其中在裏面的程式碼用IDialogContext做了兩件事情:
context.Wait() - 用來控制接下來的訊息要被什麽方法處理
context.PostyAsync - 用來回傳訊息給使用者的方法
透過這兩個方法,可以看出IDialogContext有兩個作用:
控制流程 (底層Internals.IDialogStack) - Wait代表接下來的訊息處理,還有一些別的,例如 Call 用來呼叫別的Dialog
和外界聯係 (底層Internals.IBotToUser) - PostAsync把訊息回傳給使用者。
IDialogContext還有一個重要的功能,就是用來儲存和conversation或者user有關訊息的方式 (底層Internals.IBotData)。
記錄訊息有三個等級:
ConversationData
和某個conversation有關的記錄。
PrivateConversationData
和某個user在某個conversation有關的記錄
UserData
和某個user有關的訊息 - 包含所有的channel以及conversation。
預設,這些訊息是儲存在Memory裡面,因此上了Production等級記得要做一些調整,預設有支援可以儲存在Azure的Cosmos DB或者Table Storage,當然如果有需要也可以儲存在別的地方,可以自己寫或者找套件。
這3個等級要依照需求去儲存,例如,假設是一個和使用者有關的訊息但是不和任一個conversation有關,例如他的名字,那麽可以儲存在UserData。反過來説,如果是某些訊息和某次conversation有關,例如 查旅館的時候記錄選擇了那個,那麽就可以使用PrivateConversationData。
最後,要使用這些儲存很容易,把他們當成Dictionary使用就好。
在上面提到儲存等級的時候,提到了兩個詞,Channel以及conversation。
channel之前提到過,其實就是使用者使用的溝通平臺。有可能你的bot部署到多個平臺,那麽用channel來區分可以做一些特定平臺相關處理
conversation則是某一次的交談。例如,一整串的對話就屬於一組conversation。等到10天後又過來就變成另外一組conversation。
這些資料其實有儲存在Activity裡面,我們透過emulator可以看到實際的值:
第1點可以看到是channel的值,然後conversation id來區分conversation
上面提到了這些理論,接下來我們就來實際調整chatbot。
這邊的需求是,如果沒有使用者的姓名,需要先和使用者取得,取到了之後,不管使用者輸入什麽都會直接回傳并且加上使用者的姓名。
要做到上面的需求,整個流程變成:
這邊使用了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做一些測試。
測試結果
這邊會發現有個地方滿違和,也就是我需要先輸入一個你好才觸發整個chatbot。這個其實可以在使用者剛加入chatbot的時候詢問。這個就可以用ActivityTypes.ConversationUpdate來做這些處理。
這一篇介紹了IDialogContext這個物件的作用,并且這篇著重於如何儲存個人資料。希望透過這篇對於IDialogContext的作用及用途會更清楚。
在下一篇我們切換一下,如果只能輸出一般文字對現在使用者來説還是太乾了,來看看BotbBuilder在傳輸不同資料格式上面有什麽幫助。