PIXNET HACKATHON chatbot 製作過程:(二)pixbot 學說話

Posted by Cyrus Chiu on 2017-09-17

PIXNET HACKATHON chatbot 製作過程:(二)pixbot 學說話

這系列文章主要會提到:

  • 在 facebook 平台建立 chatbot 的過程
  • 使用 api.ai 做自然語言處理
  • 使用者與 pixbot 對話記錄的文字探勘

system architecture

前情提要

前面我們提到了如何進行 facebook 訊息的傳送與接收。另外,也提到如何使用複雜一點的 payload 型態來傳送按鈕與 quick reply 等等功能。這些功能可以讓我們的 bot 與使用者互相傳送訊息,但我們也提到了,這樣並沒有辦法讓 bot 理解使用者傳送訊息的語意(也就是只有架構圖的右半部)。因此,這邊我們要介紹一個工具:「API.AI」。

不用寫 code 也可以做 chatbot

建立新的 bot 服務

API.AI 的網頁註冊好帳號之後,首先需要 create agent。如下圖,也就是建立一個機器人的服務,這邊請注意語系的選擇,這關係到 API.AI 會使用哪一個內建的語言模型來處理我們的對話,如果選擇到英文的話就別怪他沒有辦法幫你處理中文對話囉!

create_agent

在 agent 建立完成之後,就會來到 API.AI 的主畫面。後面我們會介紹 intent、entity 與 training 的部份。如果你看到下面的畫面,代表已經可以開始對 bot 進行設定了。

apiai_default_intent

Intent

上圖中,我們可以看到 Default Welcome Intent 以及 Default Fallback Intent。這兩個 intent 是當 agent 開起來就會存在的。例如,Default Fallback Intent 就是當 bot 看不懂使用者問的問題的時候所對應的 intent。

當我們尚未建立任何 intent 時,bot 本身是不認得任何文字的。API.AI 提供了一個方便的測試區塊,也就是下圖中紅色框框的部份。我們嘗試隨便輸入一句話,可以看到 API.AI 自動幫我們的輸入「貓咪為什麼這麼可愛」對應到了「Default Fallback Intent」,而該 intent 的回覆為「請您再說一遍」。

default_fallback_intent

於是我們應該為自己的 chatbot 建立 intent,不然的話你向 chatbot 說什麼,他可是什麼都聽不懂的。

在 API.AI 建立 intent 的方法非常簡單,如下圖,這是一個回應「評分標準」的 intent,我們將 intent name 命名為 score。在下面的 User says 欄位,顧名思義就是使用者對 bot 說了什麼話。這邊我們把一些可能的問法都列出來,讓這些問法都能夠 mapping 到這個 intent。在最下方的 Text response 這個欄位中,必須填上要給使用者的回覆。也就是說,當使用者輸入的句子 mapping 到這個 intent 時,chatbot 該給出的回覆。可以看到這裡寫了兩個回覆,一個是正確的評分標準,另一個是「不告訴你」。API.AI 會從這兩個句子中隨機挑選一個回覆。

apiai_score

在這邊可以看到,在畫面右邊的測試區輸入問題後,chatbot 已經可以正確把「評分標準」對應到 score 這個 intent,我們的 chatbot 就可以正確做回覆囉。按下測試區的麥克風按鈕和 play 按鈕,還可以用語音來輸入輸出,實在是很炫的功能。

我們會建議大家在還沒有開始實作 chatbot 之前,先把 chatobot 可能遇到的問題想一想,並且整理下來。並在整理完之後對這些問題進行整理,把類似的問題群聚在一起,將聚在一起的問題賦予一個共同的 intent。另外,也可以開始進行 entity 的解構,將這些問題中可能的 entity 先行提取出來。就像寫程式那樣,逐步對問題進行抽象,直到最後變成 intent 與 entity。

舉個複雜一點的例子來說,假如我們想實現訂機票的功能。那麼可能的問題如下:

  • 我要訂機票
  • 我要訂飛機票
  • 我要訂 9/9 的飛機票
  • 我要訂 9/9 到舊金山的飛機票
  • 我要訂 9/9 從台北到舊金山的飛機票
  • 我要訂 9/9 從台北到舊金山,航班號碼為 PIX123 的機票
  • 我要訂到舊金山的飛機票

上面這些句子都對應到了「訂機票」這個 intent,所以他們都該歸類在 booking_flight 這個 intent 底下。但我們可以發現,雖然都是訂機票的句子,可是有的句子講的很清楚,有的句子講的不明不白,有很多資訊需要補齊。這些需要被補齊的資訊就是 entity。

因此,我們可以想像一下「訂機票」是這樣的一個函數:

booking_flight(date=date, departure_time=departure_time, from=from, to=to, flight_number=flight_number)

其中帶有參數 date 日期、 departure_time 出發時間、 from 出發地、 to 目的地、 flight_number 航班號碼等等。然後我們不斷的透過問答,引導使用者回應出所有該回答的參數。也只有在參數都齊全之後才有辦法進行訂票。

Entity

發現了嗎,這些被解構出來的參數,其實每一個都是 entity,我們必須針對每一個 entity 分別設定。 API.AI 已經幫我們做了一些基本的 entity,降低我們在輸入 entity 時的複雜程度。

下圖顯示了,在 booking_flight 這個 intent 底下,我們將預設的 User says「我要訂20170909 的機票」之中日期的地方反白,賦予一個 @sys.date 的 entity。這就是系統預設的日期 entity,如果沒有預設這個 entity 的話,我們就得輸入所有的日期格式了!在右邊的測試區可以看到,輸入「我要訂9月9日的機票」,API.AI 仍然可以解析出這個 entity,並將他輸出成系統預設的 date 格式: 2017-09-09。

entity_date

讓我們加上地點的 entity,把例子變得更複雜一些。我們首先手動加入一些地點,例如「舊金山」,和他的同義詞。這邊我們只簡單加入幾個,然後把這些 entity 命名為 US_CITY。

在新增完 entity 之後,重新回到 intent 設定的地方。我們來看看怎麼設定 intent 中的各個 entity:

  1. 輸入一個模版句子「我要訂20170909到舊金山的機票」。
  2. 將「20170909」的部份反白,在 entity 的地方將他設定為前面提過的 @sys.date
  3. 將「舊金山」的地方反白,在 entity 的地方將他設定為 @US_CITY ,這也是我們剛剛手動創建的 entity。
  4. 將 REQUIRED 的部份打勾,代表為必答欄位。(如果使用者在這句話沒有回答的話,可以在 PROMPTS 的地方設定預設的問句來引導使用者回答。)
  5. 最後,在 chatbot 回應的地方設定「你要訂的日期是 $date 。地點是 $US_CITY 」。

然後,我們就可以在右邊的測試區看到結果囉,並且 PARAMETER 之中的 dateUS_CITY 也都有正確解析出來。

entity_setting

與 bot 平台的串接

事實上,真的可以完全不寫任何一段 code 就做好一個 chatbot。既然這樣,為什麼前面的文章裡以及 pixbot 的架構圖中又包含了不少程式呢?原因是我們希望收錄使用者的對話記錄以及一些進階功能,因此我們需要將 API.AI 給出的回應重新導向,所以才需要寫部分的程式碼。如果只是想做個可以簡單互動的 chatbot,是可以不用寫任何程式的哦!

在 Integrations 的地方,打開你想串接的平台,進入設定畫面填入該填的 TOKEN,這樣你的 bot 就可以立刻上線囉~

不是說好不寫 code 嗎

API.AI 提供了不少語言的 SDK,方便我們開發自己的應用,完成一些比較複雜的功能。這邊來整理一下 pixbot 的流程:

  1. 使用者傳訊息給 pixbot
  2. pixbot 透過 facebook webhook 收到訊息
  3. 訊息轉給 API.AI 進行語意分析
  4. API.AI 回應適合的答句給 webhook
  5. 送出從 API.AI 過來的答句給使用者

在上面的五個步驟裡,如果不寫程式的話,其實就是做了1, 3, 5三個步驟。在我們自行加上 webhook 後,我們至少必須進行兩件事:

  1. 透過 API.AI 提供的 SDK 來和 API.AI 傳送與接收訊息
  2. 解析 API.AI 傳送的訊息格式,轉為 facebook 的訊息格式

在 API.AI 的測試區可以看到一個 SHOW JSON 的按鈕,按下去之後會顯示這個問與答的資料格式:

{
"id": "f665edd1-fc49-4113-8ba5-3f11e00788d9",
"timestamp": "2017-08-10T03:47:22.994Z",
"lang": "zh-tw",
"result": {
"source": "agent",
"resolvedQuery": "我要訂 2017-09-09 到 NY 的機票",
"action": "",
"actionIncomplete": false,
"parameters": {
"date": "2017-09-09",
"US_CITY": "紐約"
},
"contexts": [],
"metadata": {
"intentId": "6aaad95d-c44d-45a8-b829-a54ed8a0c5d5",
"webhookUsed": "false",
"webhookForSlotFillingUsed": "false",
"intentName": "booking_flight"
},
"fulfillment": {
"speech": "你要訂的日期是 2017-09-09。地點是 紐約",
"messages": [
{
"type": 0,
"speech": "你要訂的日期是 2017-09-09。地點是 紐約"
}
]
},
"score": 1
},
"status": {
"code": 200,
"errorType": "success"
},
"sessionId": "4f191c49-bbe2-4b62-ab25-6c7edc81b831"
}

我們可以寫一個 parser 自行解析 json 封包裡面的重要欄位,並且透過以下利用 API.AI 的 python SDK 來進行資料的交換。這樣一來就可以完成上面 1~5 的完整步驟囉!

import apiai
CLIENT_ACCESS_TOKEN = 'YOUR_TOKEN'
# Conversation API Interface
def talk(message,uid):
ai = apiai.ApiAI(CLIENT_ACCESS_TOKEN)
request = ai.text_request()
# 此次 Hackathon Languge Model 是採用 zh_cn
request.lang = 'zh_cn'
request.session_id = uid
request.query = message
respone = request.getresponse()
rdata = str(response.read(),'utf-8')
robj = json.loads(rdata)
djson(robj)
return robj