使用C#開發LineBot(11) – Chat bot如何處理連續對話
開發對談機器人,一直有一個很關鍵的議題,就是如何處理連續性的對話。
這個問題,上課時學員問過、研討會時聽眾問過、公司同事問過、客戶問過、學校老師問過、差不多連路人甲(我是說對開發chat-bot不熟但剛好聽到chat-bot開發的路人甲)都很關心…
舉例來說
舉一個典型的例子,如果我們要開發一個聊天機器人來處理請假,那這對話大約是底下這樣:
請注意用戶開始跟chat-bot說:『我要請假』之後,chat bot進入了一個連續對談模式,在這個連續對談模式中,chat bot以一問一答的方式,跟用戶蒐集請假資訊,依序詢問用戶請假假別、代理人、時間…等資訊。
最後,蒐集完所有資訊之後,進行請假動作(上面的例子是以JSON方式把蒐集到的資訊顯示出來,沒有真的請假,但意思到了)。
需求就是這樣。這有什麼難的? 其實,還真的不很容易。
首先,如果你不想把程式碼寫死,想用同一套框架或邏輯來處理同性質的會話(conversation),這本身就有不低的難度。其次,每一個一問一答的對談之間,由於是同一個WebHook來處理的,因此要記錄狀態(state),諸如:這次問的是什麼、用戶回答的答案是對應到哪一個問句、目前的交易是屬於哪一個用戶…,這是第二層難度。
接著,用戶回答的型別如果不符合? (例如問’請假時間’,結果用戶回了一句’事假’) 該怎麼辦? 還有,就算用戶回答的資料型別正確,但邏輯上不合理(例如代理人用戶寫Tom,但公司就明明沒有Tom這個人),又該怎麼處理?
想起來,如果chat-bot要寫這種會話對談,將會是一段很恐怖的程式。
但,我實在被問過太多太多太多次這個問題了,因此,索性,找了一個心情很差的周末,我嘗試寫一個能夠處理這樣對談的框架。
怎麼用?
假設,你要處理上面這樣的對談,你可以建立一個類似底下這樣繼承自ConversationEntity的類別:
先別指摘我上面這個範例中的類別怎麼會用中文當屬性名稱,這是故意的。這是為了讓讀者更好理解,正式環境你當然可以換成英文。
但,這類別真的很好懂,是吧?
這類別表示了『請假(LeaveRequest)』這個對話將會有五個問句,第一句是問用戶『請問您要請的假別是?』,用戶回答的結果會被放入『假別』這個屬性中。第二句是是問用戶『請問您的代理人是誰?』,用戶回答的結果會被放入『代理人』這個屬性中。
其他的應該不需要解釋了吧?
注意,請假日期這個屬性的型別是DateTime,因此用戶如果回答了不合理的型別,我們的chat bot會說:
直到用戶輸入符合格式的資料為止。蒐集到所有需要的資料之後,chat bot就會結束這一輪對談:
蒐集完畢的資料,會以物件的方式回傳給我們的WebHook,我們可以進而拿來請假(或像是上面這樣的例子只是將其顯示出來)
如何開發
具體程式碼要怎麼開發呢? 別急,我們一步一步來。
首先你先定義一個前面介紹過的類別,我們稱為LeaveRequest,這個繼承自ConversationEntity的類別是用來存放請假資訊,同時也用來描述對話的順序和問句,比較特別的地方是Question與Order這兩個attribute:
前面說過,這兩個attribute用來描述這個『請假會話』的問句與詢問順序。
接著,我們撰寫一個非常典型的WebHook:
你會發現,其實和原本前面幾篇文當中的WebHook沒啥不同,唯一的差別就是12行的CIC物件,在定義這個物件的程式碼中,我們引用到了先前定義的LeaveRequest這個ConversationEntity。
CIC類別我稱為Information Collector,就是幫你使喚Line bot依照你定義的ConversationEntity來向用戶透過連續問句蒐集資訊的類別。
它會依照你定義的ConversationEntity(本例中為LeaveRequest),讓chat-bot一問一答取得資訊,至於前面提到過的什麼狀態管理(State)、型別判斷…等有的沒的,他都幫你搞定了。
16,18行是原本的WebHook,20行是定義一個取得CIC返回結果的物件。
而21行則是看到關鍵字『我要請假』,就透過24行命令CIC進入『請假會話狀態』。30行則是將每一個WebHook收到的event都丟給CIC物件,不管目前是否在『請假會話狀態』之中。CIC物件會自動判斷,把結果透過result回傳分析結果。如果目前不在對話狀態中,CIC不處理並回傳pass,如果所有資料蒐集完畢,CIC回傳done,如果判斷用戶輸入的型別部正確,CIC回傳InputDataFitError,如果用戶中斷,CIC回傳break…
因此,34-64行則是依照CIC可能的回傳結果逐一處理,例如,如果用戶輸入的資料型態不符合(要DateTime,但用戶輸入String),會進入56行的InputDataFitError,這也是前面這個例子結果呈現出底下這樣的原因:
用戶可以依序輸入資料,直到每一個資料都符合規定為止(CIC會回傳done這個result),當然,用戶也可以輸入一些關鍵字來中斷會話:
觀察上面的程式碼,你會發現其實這是CIC觸發了52行的break result的結果。
如何?透過定義一個繼承自ConversationEntity的LeaveRequest類別,搭配一個CIC物件,就可以實現這樣的chat bot對談蒐集資料的效果?這框架還過得去吧?
資料邏輯判斷
你可能會覺得… ok, looks fine. 到目前為止,我只需要定義一個ConversationEntity,它就自動幫我完成資料蒐集,看起來很炫,但實務上還有很多問題啊…
第一個就是,如果用戶輸入的資料,在型別上符合,但在邏輯上不符合怎麼辦?例如,我們想請用戶輸入『代理人』,但用戶可以隨便亂輸入,或,假設用戶輸入Tom,但公司明明沒有Tom這個人,那怎麼辦?
好問題,請看解法如下:
注意上面程式碼6-19行,我們對CIC加入了一個OnMessageTypeCheck
事件,在該事件中處理收到的用戶輸入e.ReceievedMessage來判斷即可。
例如上面這幾行程式碼,是判斷當用戶輸入的是代理人屬性,而輸入的值又不是eric的時候,就直接跟用戶說,本公司只有eric啦…
這樣,CIC就可以處裡所有的邏輯問題囉。
範例位於這裡,如果你想用自己的專案搶先玩玩的話,LineBotSDK請記得升級至 0.5.6
待解問題
有人問:如果不想讓用戶隨意回答,希望能夠用Templated Message的形式讓用戶只能用選的,那怎麼辦?
David: 下一版再加入此功能。
有人問:如果用戶堅持不按順序回答那怎麼辦??
David: 下下一版再加入此功能。
有人問:那如果OOXX該怎麼XYZ呢???
David: 下下下一版再加入此功能?
David: 還有其他問題嗎? 沒有? 很好。
相關課程: http://www.studyhost.tw/NewCourses/LineBot
LineBotSDK : https://www.nuget.org/packages/LineBotSDK
如果需要即時取得更多相關訊息,可按這裡加入FB專頁。若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。
留言
若多個使用者操作這個流程時,應該把isRock.LineBot.Conversation.InformationCollector CIC 擴展成List<>? (當然還要加上釋放資源的流程)
就是說對每個使用者來說,發送訊息後都是進到同一個CICTestController 的instance來執行
public IHttpActionResult POST() ?
不好意思對網頁程式不太熟…
不用唷。
CIC類別自動會區分並記錄用戶。 :)
想請教您 CIC 是如何實作儲存連續對話
在網站上我是用session
可是web api 我還沒想到可以怎麼處理
狀態的保存要嘛就是RAM(Session就是)要嘛就是storage(Disk, DB),沒有甚麼特別的,WebAPI是stateless,但你依舊可以用session,如果因為session有些缺點而不想用,剩下的選擇就是伺服器端的儲存體或DB了...
目前自己研究使用line@+heroku+flask框架製作了關鍵字對話
也使用chatterbot訓練對話(但這常常產生奇怪回答,資料量不夠)
請教您如果使用python語法的話,有什麼方法能實現自訂義連續對話嗎
網上查了下,python沒有類似ConversationEntity 的類去使用
因為希望做出來的機器人有漂亮的介面,又有可以記錄所有資料的功能。
感謝您的分享~
如果我想讓使用者有兩種功能(例如一個是請假,另一個是預約會議室),所以我建了兩個CIC,但是我該如何做判斷呢? 我發現兩個CIC會衝突,處理結果後面會覆蓋前面的導致錯亂。感謝您的分享~
想和您確認一下 LineBotSDK 2.0.0 版本後是否就不再支援Conversation
是的,主要是還沒全數移植到.net core上,還需要一點時間唷...