如何以PHP 打造一個Facebook 的解籤機器人

    網路上常看其他網友或公司建置具有人工智能的聊天機器人,所以有點手癢,想嘗試看看。網路上大部分的分享者都寫說FB的官方教學已寫得很詳細了,不過我在建置的過程中,由於完全無經驗,在網路上爬了一些資料,但仍不停的卡關,最後似乎終於讓我貫通了。這些卡關的部分,有一部分是 web 開發對我而言,雖非第一次,但經驗總是不多;另一部分是 wit.ai,在我看教學文時,wit.ai 平台的 story 的功能未被拿掉,而我嘗試使用時,此頁籤已下架,我又對 AI 平台架構不是很懂,就這樣碰碰撞撞,摸索出一些功能規則出來。以下就要建置一個略具智能的解籤機器人為前提,我以 PHP 的開發經驗做一個建置記錄。

    我想做的機器人應算是我天機道腦波靈籤在網路上的延伸,我希望機器人可以在專頁與大家做兩個簡單的互動:

    1. 問候:顧名思義,就是大家在 messenger 上打招呼時,它能自動的回應問候。
    2. 解籤:大家可以在 messenger 上問籤的內容與解釋,例如:請問第一籤是什麼意思,或甲子籤是什麼意思?它可以提供資料庫中的第一籤的籤詩與解釋。

     05

    一、   建立粉絲專頁

    FB 的聊天機器人目前只能在粉絲專頁中建置,這專頁就是用來當作機器人的身份,沒啥特別的,若沒有的話,就建置一個就是了。

    01 02
    03

    本範例我就使用我的腦波靈籤的專頁做 messenger 的解籤機器人。

    二、   申請臉書應用程式

    若還沒成為臉書的開發者的話,可先到 Facebook for Developer 申請成為開發者,然後[新增應用程式]。若已成為開發者,可點選左邊的選單中的[管理應用程式],進入應用程式管理頁面,並[新增應用程式]

    06 06

    輸入安全驗證。

    08

    在新增產品的頁面選擇Messenger,將滑鼠移到圖示上,按[設定]進入到 Messenger 的設定頁面。

    09

    設定頁面中主要要注意的部分有二個地方:一、權杖產生;二、Webhooks。而內建自然語言處理部分,因為目前只支援英文,所以有開與沒開,並沒有太大的差別。

    12 

    Messenger 應用程式審查部的設定,若是想要供大家使用的話,就必須提交審查,像我的這個應用,只是純聊天解籤,選擇第一項[pages_messenging]即可。

    對於設定頁面先說明到此,接下來我們先做 Messenger Webhooks 的界接程式。程式完成後,我們再回來說明如何接入 Messenger

    三、   建置 Web 服務器

    要能接入 Facebook messenger,必須要透過 Webhook 程式。什麼是hook?它是一種掛勾機制,其基本概念就是我們在系統中埋個感測器,當外界事件觸發這個感測器時,就通知我們的程式進行處理。我們要實作自己的服務機器人,這些服務的資訊是要我們自己建置與準備的,而Facebook 也不可能開放給我們直接在其服務器上建置各自的服務,所以就透這種掛勾機制,開放一些訊息界面供我們選擇;當這些我們選擇的訊息被觸發時,就會轉到我們的服務器上運行,並將處理結果返回給 Facebook 回應給用戶。

    基於如此,我們需要一個 Web 服務器,基於網路安全,Facebook 要求必須走安全通道(https)。網路上也有一些能提供https 的雲端應用平台(Cloud Application Platform),如:Cloud9Heroku。若你是使用這種平台的,可以直接看下一章節。本章將介紹如何在 Ubuntu + Apache + PHP + Mysql 的環境使用 Let’s Encrypt 的免費 certificate

    我的機器環境配置如下:

    l   OS: Ubuntu-Mate 16.04

    l   Web Server: Apache 2.4.18

    l   PHP: 7.1

    l   MySQL: 5.7.19

    現在 Let’s Encrypt 網站上有提供一個 CertBot 的自動化安裝套件,進入網站後,根據你的作業系統與平台選擇下載執行;或照下列步驟執行:

     

    1. $ sudo add-apt-repository ppa:certbot/certbot
    2. $ sudo apt-get update
    3. $ sudo apt-get install python-certbot-apache
    4. $ sudo certbot –apache -d 網域名稱
    5. 我們可以設定自動更新 Let’s Encrypt SSL certificate

         $ sudo crontab -e

    於檔案中加入下面代碼,代表每天早上5:30執行自動更新

    15

    四、   編寫 Webhooks 程式

    由於我剛開始時想自己對這種 web app 並不是那麼的熟悉,所以在網路上爬文,找一些別人寫好的 SDK 或範例,但我發現大部分都是套了一個 framework(如:Laveral, CodeIgniter…) [1]在使用的,我們在編寫代碼時就得配合 freamwork rule。若 App work 時,對 framework 不熟的我,常摸不著頭緒,debug/trace 花了不少時間。

    後來,我想就乾脆把 app 當一般的 console 程式寫吧~,少了 framework其實這個 hook 程式其實並不複雜。

    <?php

    //
    FBWit.ai 界接的參數
    $hubVerifyToken = 'AccessToken'; //
    這裏寫的字串,後面我們設定 webhooks 時要一致
    $accessToken = “
    粉絲專頁存取權杖”;

    // check token at setup
    if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] == 'subscribe') {
        if($_REQUEST['hub_verify_token'] == $hubVerifyToken) {
            echo $_REQUEST['hu_challenge'];
            exit;
        }
    }

    // 接收來自 Facebook input
    $input = json_decode(file_get_contents('php://input'), true);
    $senderId = $input['entry'][0]['messaging'][0]['sender']['id'];
    $messageText = $input['entry'][0]['messaging'][0]['message']['text'];

    // 收到什麼就回傳什麼

    $answer = $messageText;

    $response = [
        'recipient' => [ 'id' => $senderId ],
        'message' => [ 'text' => $answer ]
    ];

    $ch = curl_init('https://graph.facebook.com/v2.6/me/messages?access_token='.$accessToken);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($response));
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    curl_exec($ch);
    curl_close($ch);

    寫一個 webhook,代碼就這麼幾行了,這好像也是 Facebook 的教學範例的執行結果,只是 Facebook 用的是 Node.js 罷了。

    這裏有兩個 token 要設定,一個是 $acessToken,它是粉絲專頁的存取權杖,如下圖,先選擇要 hook 的專頁。

     16

    而後 Facebook 會要求個人帳戶的存取權限。

    17 18

    確認後,就會出現該專頁的存取權杖。

     19

    另一個是 $hubVerifyToken,這個變數的內容自訂,這兒設定的字串,接下來在設定 Webhooks 中有一相應欄位,必須填寫一樣的。完成後,將代碼存檔(index.php)上傳到服務器。接下來我們再回到 Facebook 的應用程式設定頁面,進行 Webhook 的設定。

    五、   設定 Webhooks

    點選[設定 Webhooks]進入設定頁面。

     20 21 

    回呼網址輸入:https://伺服器位址/index.php。這兒有個驗證權杖,輸入的就是代碼中(index.php) $hubVerifyToken 的值,兩邊要一致,驗證才會過。訂閱欄位就是我們要掛勾(hook)那些訊息,這兒我們選第一個 messages 即可,未來若有要處理其他的訊息,可適時的補上。接下來按[驗證並儲存]將設定寫入。這兒 Facebook 會先上面的程式內容有無錯誤,能否正常執行。若無法運行,它是不會寫入設定的。這兒就要 debug 啦。先看看 apache 有沒有起來,再看看 https是否 work,若都沒問題,就看 /var/log/apache2/error.log[2],看程式有沒有錯誤。

    若設定成功後,就可以透過 messenger 發訊息給我們前面訂閱的粉絲專頁了,若專頁回覆剛輸入的內容,就表示正確了,你已完成第一支 messenger 聊天機器人了。

    後續若想要再掛勾到其他新開發的程式,怎麼做呢?很簡單,頁面左邊應用程式主控板的商品菜單下有個 Webhooks,點選進去後按[Edit Subscription]就會跳出 Webhooks的設定對話框,讓我們重新設定回呼網址與驗證權杖。

    六、   Wit.ai App

    前面的App 只會重覆你的輸入,啥都不能做,相當於剛出生的嬰兒,接下來,我們可以怎麼教它呢?最簡單的方式,就是提供使用者關鍵字文本,系統根據關鍵字的比對,做相對應的動作,例如:關燈,系統比對到用戶輸入「關燈」這個關鍵字,就執行關燈的指令。但這麼做,就不像聊天啦!真的聊天,應是輸入像:我想關客廳的燈、幫我開房間的冷氣好嗎?一類的所謂的「自然語言」。

    目前人工智能當道,語音與文字識別的人工智能平台群起,如 Apple siriMicrosoft cortana,這兒我們用的是 Facebook2015年購併的 wit.ai,為什麼選它呢?只是因為它被Facebook 購併了,也算是 Facebook的一部分了,既然我們是實作 Facebook messenger,我想它應該整合性較好吧!這是我原本的想法,後來,我才注意到 Facebook Messenger 在自然語言的處理上,目前僅支援英文,換句話說,若是溝通用英文的話,而你在 wit.ai 的平台上建的 App 模型也是以英文為主的話,基本上應是一行代碼都不用寫,就可以直接用了。但我用的是中文,所以,還是需要接入到我們的代碼中,自行處理的。

    接下來,我們來看看 wit.ai 怎麼用。

    第一步、申請 wit.ai 帳號

     

     22

    可以使用 Facebook github 的帳號登入。登入後就會進入到個人的 Dashboard 畫面,若你是第一次登入,系統會直接跳到下一步 - 創建 App

     

    23

     

    第二步、創建 wit.ai App

    Dashboard 畫面按右上角的「+」,進入 App 建立頁面,進行簡單的設定,基本上只要設定名字、描述及語言等資料後,按 Create App,系統就會創建一個空白的 App。

     

    24

     

    這兒我們可以看到語言設成 Chinese 的話,系統目前仍是 Beta,我想這也解答了為何我的解籤機器人會智能不足。

    第三步、創建對話模型

     

    25

     

    當我們按下 Create App 後,平台便會將我們帶到上圖這個頁面,我在網路上爬 wit.ai 的教學文時,wit.ai 平台原先似乎並不是如上圖的介面,而是以 Story 的方式,以情境式對話的方式進行模型的訓練工作,似乎近日系統改版了Story 的實作方式取消了[3],改以上圖的介面。不使用 Story GUI 我還真花了點時間了解 wit.ai 的操作教學文中大家不免俗的講 wit.ai 的文件寫得不錯,很詳細但大家還是寫了教學文,為什麼呢因為不是中文在這兒我就個人研究所得說明一下它的 senario

    此界面主要在專注於分析 user-input 的文本,至於該如何response就交給 developer 自行處理了。所以當我們輸入一句話例如我想問第一籤的意思我們首先要知道這是一句話這不是廢話而是我們要教 wit.ai這是一句話我們要將這句話歸類所以像這樣一句話我會建一個 intent這個 intent poem告訴 wit像這樣類型的敘述句,我們可將其分類成 poem但如此,wit還是不知道其實要找的關鍵字是「第一籤」,所以我們就可以把「第一籤」當作關鍵字。上述步驟,我們就可以在”User says” 欄位輸入我想問第一籤的意思,下方 Add a new entity 部分先新增一個 intent value 設成 “poem”接下來把第一籤反白,就會看到顯示”Create an entity for 第一籤,鼠標點擊此處,輸入”poem_number”並把 value設成”1”,按下”Validate”完成第一次的學習。

     

    26
    27
    28

     

    完成後,你會看到如下圖顯示的畫面,系統新增了二個 entity – “intent””poem_number”這兩個 entity的屬性不同,intent trait所謂 trait 就是特徵,它是學習語句的特徵的,而”poem_number”則是屬於free_text&keyword”

     

    29

     

    我們可以在 poem_number 右方的箭頭點一下,進入”poem_number”的編輯畫面。

     

    30

     

    我們可以在 Synonyms 中新增同義詞,當然,也可以在 Keyword 中新增 Keyword我們可以在 Understanding 頁面多做些訓練,建立些斷詞,增加 wit 的中文能力。若訓練好後,我們可以到 Settings的頁面輸入測試模型。

    第四步、測試 wit.ai

    Settings 的頁面最上方,wit 平台備好 curl 的命令列,我們可以輸入資料,再複製 curl 的命令到 server 上測試我們建的模型。

     

    31
    32

     

    若能正確識別,就會如上圖所示,它以 JSON 的格式回覆:我們這句話是 poem_number值是”1”

    七、   界接 Messenger chatbot wit.ai

    模型訓練好後,我們就要接入我們的 chatbot我的 scenario 是這樣的:若讀到 poem_number 的值,我們就去數據庫查詢相對應籤號的籤詩及解釋。所以接下來,我們就要在 index.php 中做以下幾件事:

    1. 連接數據庫

     

    // 建立 mysql 連線
    $server=’your database server ip';
    $id=’your database account’;
    $pwd='your login password';
    $dbname='your databasename';
    $link = mysqli_connect($server, $id, $pwd, $dbname) or die("無法開啟MySQL資料庫連結!");

    mysqli_query($link, 'SET CHARACTER SET utf8');
    mysqli_query($link, "SET collation_connection = 'utf8_general_ci'");

     

    2.透過 php_curl user-input context餵給我們訓練好的

     

    $wit_url = "https://api.wit.ai/message?v=20171020&q=".$messageText;
    $wit_auth = array("Authorization: Bearer ". $witToken); $ch = curl_init(); curl_setopt($ch,CURLOPT_URL, $wit_url); curl_setopt($ch, CURLOPT_HTTPHEADER, $wit_auth); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch);

     

    3. 解析回覆的 JSON 格式的

     

    $response = json_decode($response,true);

     

    4. 依據 wit.ai response 來決定 answer.

    $intent = $response['entities']['intent'][0]['value'];
    $poem_num = $response['entities']['poem_number'][0]['value'];
    if ($intent == "Welcome") {
        $answer = "您好,我是神算子,有什麼需要我幫忙的嗎?";
    } else if ($intent == "poem") {
        if (is_numeric($poem_num) ) {
            $poem_num = (int)$poem_num;
            if ($poem_num > 0 && $poem_num <= 60) {
                // 有取得籤號, 我們回覆籤詩及解釋
                $sql = "SELECT content, explanation, name FROM data WHERE id=". $poem_num;
                $result = mysqli_query($link, $sql);
                while( $row = mysqli_fetch_row($result) ){
                    $answer = $row[2]."\n籤詩:\n".$row[0]."\n"."解釋:\n".$row[1];
                }
                mysqli_free_result($result);
            } else {
                $answer = "很抱歉, 我們的籤詩只有一~六十籤!";
            }
        } else {
            $answer = "您是要問籤詩內容嗎?請告訴我您要問那支籤!";
        }
    } else {
        if (is_numeric($poem_num) ) {
            $poem_num = (int)$poem_num;
            if ($poem_num > 0 && $poem_num <= 60) {
                // 有取得籤號, 我們回覆籤詩及解釋
                $sql = "SELECT content, explanation, name FROM data WHERE id=". $poem_num;
                $result = mysqli_query($link, $sql);
                while( $row = mysqli_fetch_row($result) ){
                    $answer = $row[2]."\n籤詩:\n".$row[0]."\n"."解釋:\n".$row[1];
                }
                mysqli_free_result($result);
            } else {
                $answer = "很抱歉, 我們的籤詩只有一~六十籤!";
            }
        } else {
            $answer = "很抱歉, 我不懂您的意思";
        }
    }

    5. 透過 php_curl answer 回給 messenger

     

    $response = [
        'recipient' => [ 'id' => $senderId ],
        'message' => [ 'text' => $answer ]
    ];
    $ch = curl_init('https://graph.facebook.com/v2.6/me/messages?access_token='.$accessToken);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($response));
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    curl_exec($ch);
    curl_close($ch);

     

    完整的代碼已上傳到 Github,在此下載

    八、上線

    將代碼完成後,就可以在 Facebook messenger 中搜尋我們建立的專頁,並發送訊息測試看看了~

     

    33 34

     

    不過此時僅只有開發者自己可以使用,Facebook 並未將我們的 App 佈署上線;若測試完畢,要供其他人使用,記得要把 App 發佈。

     

    35

     

    九、結論

    以上是我在使用 PHP Facebook messenger bot 的一個建置的一個心得記錄。若真要建置一個企業或個人品牌的 messenger bot為擴充性與維護性等因素考量,搭配自己上手的 framework CodeIgniterLaveral一類還是不錯的選擇。

    在解籤機器人上線後,我覺得它的智能還是很低的,究其原因,我想應是與中文的文本有關,wit.ai對中文如何斷詞是不懂的,基於如此,改進方向有二:

    1. 可透過結巴中文斷詞算法將輸入文本(進行斷詞
    2. 中多建立一些 entity

    由於有了不同的文本斷詞及 entity我想我們回應可能性便更多了,對於回應的文本內容就更多元化,相對的感覺智能就會有所提升。

    我做解籤機器人的目的在於希望未來有機會因應 user-input 的文本內容(文字、語音),讓 output 更客製化與準確,此部分僅是一個開始,也歡迎有興趣的朋友一同來參與、討論與開發。

    最後,我這樣使用下來覺得 wit.ai 並不是想像中的好用,還有其他如 dialogflow[4]tensorflow……都是不錯的選擇。

    參考資料

    1. Huli’s Blog - http://huli.logdown.com/posts/709641-teaching-facebook-messenger-api
    2. Python開發Facebook Bot - https://medium.com/dualcores-studio/python開發facebook-bot-26594f13f9f7
    3. Ubuntu 設定 Let’s Encrypt 免費 SSL 網站證書 - https://www.imnobby.com/2017/09/20/-ubuntu-設定-lets-encrypt-免費-ssl-網站證書/
    4. Linux (Ubuntu) 導入 Let’s Encrypt (for Apache) - https://blog.nami.idv.tw/?p=584
    5. [教學] 申請Let’s Encrypt憑證與啟用https (Nginx) - https://xenby.com/b/101-教學-申請lets-encrypt憑證與啟用https-nginx
    6. 如何 10 分鐘開發一個 Facebook Bot (PHP) - https://medium.com/coding-cheatsheet/如何-10-分鐘開發一個-facebook-bot-php-fc9fc3b132c6

    [1] 例如 pimax fb-messenger-bot(https://github.com/pimax/fb-messenger-php). 就是用 CodeIgniter 做的,網路上亦有人用它寫教學文 - 如何 10 分鐘開發一個 Facebook Bot (PHP)

    [2] 此資料夾路徑是基於 ubuntu linux

    [3] 研發團隊在2017.07.27時公告因為團隊希望能更專注於自然語言處理使系統模型能更準確可靠與可擴展他們決定放棄使用 story UI

    [4] Api.ai2017.10.10起改名為 Dialogflow, 參見:https://blog.dialogflow.com/post/apiai-new-name-dialogflow-new-features/

     

    天火資訊工作室

    地址:新北市中和區泰和街38巷30號

    電話:(02)2242-6409