在上一篇透過使用Project Template建立出一個EchoBot出來,并且透過了bot emulator瞭解了如何和chatbot做測試。
這篇將會深入一些,看看BotBuilder的組成以及一些比較重要的class。
這篇的程式碼github頁面是alantsai-samples/mhat-hotelbot:blog/chapter-04
首先看一下上一篇建立出來的的VS 專案内容。裡面東西不復雜,可以看到只有幾個資料夾以及class:
最基本的BotBuilder專案結構
我們可以看一下:
之前有提到過,BotBuilder是以Asp .Net Web Api為基地,因此一定會有一個Controller作爲進入點。而這個class就是我們整個服務的進入點。
往裏面看可以看到只有一個Post方法,并且接的參數形態是Activity。然後依照不同的ActivityType,有不同的處理:
public async Task Post([FromBody]Activity activity)
{
if (activity.GetActivityType() == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
當ActivityType是訊息類型的時候,我們會呼叫RootDialog,要不然會到另外一個方法裡面處理不同類型的ActivityType。
那Activity這個class是什麽東西呢?
還記得之前的一張圖嗎:
整個Bot Framework的結構
我們提到過,爲了支援不同平臺,BotBuilder會用一個統一格式作爲最後的資料結構,而Activity或者更抽象的說IActivity就是這麽一個統一的形態。
因此BotBuilder每一次收到的都是一個Activity,而Activity又有分形態:
ActivityTypes.Message
最常見的一種形態,只要是使用者傳入的訊息都是這種。
ActivityTypes.ConversationUpdate
當有使用者加入或者退出chatbot的時候
ActivityTypes.DeleteUserData
當使用者退出chatbot并且要求刪除資料。這個可以用來清理chatbot對於某個使用者記錄的訊息。
ActivityTypes.Typing
當使用者正在打字的時候,還沒送出的類型。這種使用情景比較少。
ActivityTypes.Ping
一般來説這種都是平臺用來確定chatbot是不是還活著。
Activity取決於不同的Type也會包含不同的資訊。例如,如果是ActivityTypes.Message,那麽取決於是什麽類型的訊息(文字、圖片或語音)那麽從Activity取得資料的方式也不同。
最基本的文字類型,可以透過Text這個Property取得值。其他的訊息類型後面有遇到在介紹。
Dialog是BotBuilder裡面的一個概念,可以簡單理解成爲:專門處理一件事情的最小單位,所以例如我的chatbot有查訂單和查旅館兩個功能,那麽就有2個dialog分別處理對應事情。
Dialog在後面拆分邏輯的時候會有更加詳細的介紹,以目前來説可以理解為RootDialog是我們的文字訊息處理的主要邏輯。
一個正確的Dialog有兩個條件:
實作IDialog
把class訂位Serlizable
整個程式碼如下:
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object>result)
{
var activity = await result as Activity;
// Calculate something for us to return
int length = (activity.Text ?? string.Empty).Length;
// Return our reply to the user
await context.PostAsync($"You sent {activity.Text} which was {length} characters");
context.Wait(MessageReceivedAsync);
}
StartAsync是IDialog本身要求實作的部分,他透過context.Wait表示等待下一個訊息要直接由 MessageReceivedAsync做處理。
而在MessageReceivedAsync裡面則是從Activity裡面取得了文字内容,并且透過context回傳了文字本身内容以及長度。
最後呼叫了一個context.wait,并且指向同一個方法。換句話説,變成了無限回圈,下一個訊息還是由MessageRecievedAsysnc來處理。
IDialogContext是一個輔助上下文的物件 - 裡面可以取得和這個使用者相關的一些訊息,和控制接下來的chatbot流程。下一篇將會介紹這個部分,因此暫時忽略。
調整chatbot
有了以上的資訊之後,對於如何修改程式就有了基本概念。我們要把理論變成實作。
假設今天有了一個需求:如果使用者輸入了Hello,chatbot回傳World,要不然就保留目前的回傳内容。
那麽怎麽修改呢?
從上面的理解知道所有邏輯都在RootDialog裡面,因此如果在裡面加入一些判斷邏輯就可以啦:
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (activity.Text == "Hello")
{
await context.PostAsync("World");
}
else
{
// Calculate something for us to return
int length = (activity.Text ?? string.Empty).Length;
// Return our reply to the user
await context.PostAsync($"You sent {activity.Text} which was {length} characters");
}
context.Wait(MessageReceivedAsync);
}
測試結果
IActivity
整個BotBuilder接受以及傳送的一個共用的資料形態。
IDialog
一個最小單位的功能邏輯 - 透過Dialog可以做到Separtation of Concern。
IDialogContext
處理Chatbot上下文以及流程的物件。
這篇對於整個BotBuilder的運作以及幾個重要物件做了介紹,并且透過一個小修改調整了chatbot的邏輯。
可是接下來有個很重要的問題是,我如果要記錄和使用者有關的訊息要怎麽存?
下一篇,將會看一下專門處理上下文的物件IDialogContext。