1. 程式人生 > >使用OLAMI自然語言開放平臺提供的API介面製作自己的語音助手

使用OLAMI自然語言開放平臺提供的API介面製作自己的語音助手

告訴你如何使用OLAMI自然語言理解開放平臺API製作自己的智慧對話助手

我們經常在電影中看到機器和人對答如流,隨著越來越多自然語言開放平臺的出現,IT愛好者製作一個自己的APP或者小玩具等逐漸可以變為現實。
自然語言對話即你的APP或者你製作的工具、機器人等能夠對使用者輸入的語音或者文字做出準確的迴應。

比如,在微信公眾號中,經常要求使用者通過輸入1、2或者其他關鍵字來獲取相應的服務,而對於句子卻無法正確理解。比如,你輸入“中秋活動”,這個幾個字如果符合關鍵字的要求,那就會彈出相應的服務。但如果你輸入的是“我想參加今年的中秋活動”,“參加中秋活動”等可能就無法進入活動網頁了。

再比如,很多智慧玩具,你只能跟它進行簡單的對話,因為它也是隻能抓取簡單的關鍵字。

實際上,我們希望的是做到像許多語音助手一樣,能夠正確識別使用者說的話,並做出對應回覆,甚至希望能夠理解上下文。

比如,使用者問:今天上海的天氣

應用回答:今天上海的天氣為。。。。。

如果使用者接著問: 那北京呢

你的應用能直接回答:今天北京的天氣。。。。。

而不需要輸入完整的句子“今天北京的天氣”。

目前提供智慧語音語義理解的API介面不少,使得自己製作語義語音小助手成為現實。各開放平臺的情況可以看我的另一篇部落格:熱門自然語言理解和語音API開發平臺對比

要使用OLAMI開放平臺首先要了平臺的提供的功能和我們需要做的事情
我使用微信小程式開發工具寫了一個工具類的小程式,可以參考我的另一篇部落格 微信小程式+OLAMI自然語言API介面製作智慧查詢工具–快遞、聊天、日曆等

下圖簡單描述了OLAMI語言理解開放平臺的工作過程,我們要做的工作其實就是圖中紅色的標誌1和標誌2,即寫語法和寫自己的應用程式。而你的應用程式使用者只需要輸入想說的話,即自然語言句子,就可以得到你的應用程式處理的結果。

OLAMI語義理解流程圖

一.瞭解幾個關鍵詞的含義

  1. 語法,我的理解就是描述自然語言句子的一套規則,看官方文件也確實是這樣。比如:“幫我查個快遞”,你用一堆符號描述出來,這個組合起來的符號就叫語法。
  2. 自然語言,也叫語料。這個不用多說,我們平時說的話是什麼,那就是什麼。
  3. 語料的有效資訊,即輸入的句子中包含的關鍵資訊,你的應用程式獲取到這些這些關鍵資訊之後,可以做相應的處理。比如:‘請你幫我查個快遞’,其實這句話隱藏的含義是“查快遞”,其他的詞彙資訊不需要關心。
  4. Answer—這個在OLAMI平臺中指你寫的語法的預設回覆,不需要應用程式做深度處理的,這個暫時可以忽略。
  5. 應用程式—-這就是你自己的APP了。
  6. 結果—-就是使用者輸入一句話,你的APP回覆結果。比如,使用者輸入’查快遞1234’,你應該回復‘1234這個快遞的物流資訊’。再比如“今天的天氣”,你應該列出今天的天氣狀況,而不是昨天的天氣,也不是今天的日期。

二 寫語法

   呼叫API介面之前,首先要寫自己的語法,也就是你要支援哪些句子,當然你也可以直接使用OLAMI平臺提供的內建語法,如果它的語法符合你的要求的話。在這裡,我說說怎麼寫自己的語法。

  1. 首先在官網上註冊,然後進入開發系統中“我的應用”。

應用介面

2.確定自己的模組

   在正式寫語法之前,首先得弄明白怎麼寫語法,寫什麼樣的語法。

   第一步得選擇你的應用程式支援的模組,比如是查天氣、播放音樂、還是智慧家控制。
這個模組,其實可以稱之為領域,因為你不可能把人類所有的語言都涵蓋,你的APP一般都是針對某個或者某幾個領域。我選擇的是快遞APP。所以我要支援的都是快遞的說法。

   確定好模組之後,你要思考一下你的應用程式希望從使用者的說法中得到哪些有用資訊,以及使用者會有哪些操作。比如快遞查詢,我需要有“運單號”,“快遞公司名稱”這兩個有效資訊,另外,我的應用程式僅提供快遞的查詢業務。

   我大致知道如果使用者說“幫我查一下圓通快遞12344”的時候,包含的有效資訊很全,我可以直接輸出物流資訊給他。但如果使用者僅僅說“我想查快遞”,這時他僅表達了想查的意願,我的應用程式應該提示使用者輸入快遞單號。

   在OALMI的語法中,使用”Slot”來抓取有效資訊,它就像一個函式的引數,它的內容由使用者決定。比如運單號和快遞公司名稱,每個使用者的內容都是不同的。
   因此進入OLAMI的NLI系統之後,我首先“新增”一個模組,名字為”expressage”,然後進入這個模組開始寫語法。

3.確定有效資訊Slot

   就如上面所述,我需要“運單號”和“快遞公司名稱”這兩個關鍵資訊,因此,我定義了”expnumber”和”expname”這兩個Slot.

   我選擇的型別均為’ext’,因為使用者有可能會直接輸入運單號,而運單號的格式無法確定,所以我選擇ext來抓取。
    快遞公司的名稱也是有限的,其實選擇internal格式的就可以了,但是我選擇後面通過ext賦值的方式給slot賦值,這樣有利於語法維護。(很拗口是吧?可以暫時不管)

4.確認APP的功能

    你的APP準備提供什麼功能呢?查詢?顯示?開啟?關閉?OLAMI語法需要用Modifier來描述操作資訊。其實也就是一個標誌來告訴應用程式這句話的意圖是什麼。
比如“開啟燈”,意圖是開啟,可以定義一個modifier “open”.
“開啟空調”的意圖同樣是開啟,你只要用沿用已經定義的“open”即可。
我的快遞APP很簡單,我就是提供查詢功能,因此我定義一個global modifier ‘query’.

5.瞭解Grammar,Rule,Template.

   其實剛開始只要知道Grammar和Rule就可以了。

   Rule即同義詞匯的集合,詞彙之間用’|’隔開,表示或的關係。

   Grammar即描述你要匹配的句子的語法。

   比如你希望匹配句子”查詢快遞”,可以將“查詢”的同義詞定義一個Rule, “快遞”同義詞匯定義一個Rule,我建議名稱能使用中文就使用中文,這樣看起來比較直觀。下表中是可以匹配”查詢快遞”這句話的Grammar相關定義。

名稱 型別 內容
查詢_動詞 Rule 查詢|查一下|查查|查
快遞_名詞 Rule 快遞|速遞|物流|速運|快運
expname Ext Slot
expnumber Ext Slot
查快遞1 Grammar <查詢動詞><快遞名詞><{@=query}>
查快遞名稱運單號1 Grammar <查詢動詞>< expname ><快遞名詞><{@=query_name_num}>

   你在寫Grammar之前要確保Grammar中需要的Rule,Slot,Template已經定義好,並且想好自己的操作modifier.
   每寫好一個Grammar可以通過“例句測試”檢查你要支援的句子是不是被當前的Grammar匹配,這個Grammar希望支援的句子都包含進去了,你再提交,然後釋出。
釋出之後你才能通過API介面進行訪問。

三 開發自己的APP

  1. 你需要從語義理解API介面獲取什麼資訊?

   https的返回,比如status就不再介紹了。

   說白了,開發平臺解析你的語法之後,就是會告訴你這句話中的Slot和modifier,以及你的模組名稱。
   Slot根據你選擇的型別不同,你獲取的內容不同。比如ext型別的,你可以拿到slot的名稱和slot的值。

   Datetime型別,即你的Slot是時間,你還可以拿到時間的毫秒數,起始時間等。

   Number型別,表示你只會抓取數字,會得到數字的計算值等。

   Modifier就是你自己定義的要支援的操作,只要按照他們規定的格式命名就好。

   2.建立應用

   應用可以包含多個模組,具體包含哪些模組也是由你自己決定。OLAMI預設支援了“聊天”,“百科”,“查詢日期”三個模組。如果你不需要可以去掉。內建模組的說明見

   點選下圖中的配置模組新增自己寫的模組和你希望新增的內建模組。比如對話系統模組中的nonsense就是聊天用的。你可以點配置模組右邊的”測試”,輸入要查詢的句子就可以看到結果。

   如果測試結果能正確返回,就表明API介面也可以獲取同樣的結果。

   比如我在輸入框中“查快遞”就可以看到JSON格式的輸出,如下圖顯示:

3.檢視應用的key

   這個key就是你訪問API 介面的鑰匙,在你的應用中點選”檢視key”就可以看到了。

4.訪問自然語言解析API介面

   API介面是https協議,相關說明,我就不再贅述了。

   我使用的是小程式訪問,相關程式碼如下,你程式碼下載包的input.js裡可以看到:

function parseCorpus(corpus,object) {
    var usekey = Appkey;
    var usesecret = Appsecret;
    if (object.data.dialogtype == chat_type){
    usekey = ChatAppkey;
    usesecret = ChatAppsecret;
    }

  //獲取sign的MD5值
  object.setData({
    text: '請稍後......'
  })
  var timestamp = new Date().getTime();

  var originalSign = usesecret + "api=" + api + "appkey=" + usekey + "timestamp=" + timestamp + usesecret;
  var sign = MD5.md5(originalSign);

  var rqdata = { "data": { "input_type": 1, "text": corpus }, "data_type": "stt" };
  console.log(JSON.stringify(rqdata))
  console.log('\r\n')
  wx.request({
    url: requestUrl,
    data: {
      appkey: usekey,
      api: api,
      timestamp: timestamp,
      sign: sign,
      rq: JSON.stringify(rqdata),
      cusid: userId,
      changebuttoncolor: "#d0e0e3"
    },
    header: {
      'content-type': 'application/x-www-form-urlencoded'
    },
    method: 'POST',
    success: function (result) {
      var data = result.data.data;
      if (result.data != null && result.data.status!=null&&result.data.status=='ok'){
        HandleOLAMIresponseData(data.nli[0], corpus, object);
        console.log('尤拉蜜有效資料', result.data);
      }else{
        console.log('尤拉蜜返回失敗', result.data.status);       
        object.setData({
          text: API_data_error
        })
      }
    },

    fail: function ({errMsg}) {
      console.log('request fail', errMsg)
      object.setData({
        text: API_data_error
      })
    }
  })


};
  1. 根據獲取到的Semantics內容提供服務

我的處理邏輯是根據不同的modifier進行相應的操作,不同的操作下又要檢查slot,程式碼如下:

    function HandleOLAMIresponseData(data, corpus, object)
    {
         var textData='';  //text文字框要show的內容
         var semantics = data.semantic;
         if (semantics == null || semantics.length==0){
           if (data.desc_obj.result != null && data.desc_obj.result.length != 0 && data.desc_obj.status==0)  {
             if (data.type == 'joke' || data.type == 'cooking'){
               textData = data.data_obj[0].content;
             }else
               textData = data.desc_obj.result;

           }else
              textData='抱歉,我還理解不了你說的話。';
           object.setData({
             expresshead: '',
             text: textData
           })

         }else {

           for (var i = 0; i < semantics.length; i++) {
             var tempSem = data.semantic[0];
             if (tempSem.app == expressAPPname) { //僅處理快遞模組的語義
               //處理modifier
               var mods = tempSem.modifier;
               if (mods.indexOf("query") > -1)
                 expAppinfo.OPT = OPT_QUERY;
               else if (mods.indexOf("query_num") > -1)
                 expAppinfo.OPT = OPT_QUERY_NUM;
               else if (mods.indexOf("query_name") > -1)
                 expAppinfo.OPT = OPT_QUERY_NAME;
               else if (mods.indexOf("query_name_num") > -1)
                 expAppinfo.OPT = OPT_QUERY_NUM_NAME;

               //獲取slots,即快遞公司名稱和運單號
               var slots = tempSem.slots;
               if (slots != null) {
                 for (var j = 0; j < slots.length; j++) {
                   var tempslot = slots[j];
                   if (tempslot.name == expNumSlotName) { //運單號
                     var numslot = new APPSlot();
                     numslot.name = expNumSlotName;
                     numslot.value = tempslot.value;
                     expAppinfo.numSlot = numslot;
                   } else if (tempslot.name == expNameSlotName) {//快遞名稱
                     var nameslot = new APPSlot();
                     nameslot.name = expNumSlotName;
                     nameslot.value = tempslot.value;
                     expAppinfo.nameSlot = nameslot;
                   }

                 }

               }
               //handle Operations
               switch (expAppinfo.OPT) {

                 case OPT_QUERY:
                   textData = '請提供您的運單編號。';
                   object.setData({
                     expresshead: '',
                     text: textData
                   })
                   break;
                 case OPT_QUERY_NUM:
                 //檢測是否存在快遞名稱
                   if (expAppinfo.nameSlot != null && expAppinfo.numSlot != null){ //採用快遞編號+快遞公司方式查詢
                     //獲取快遞公司名稱
                     var expname = getExpCode(expAppinfo.nameSlot.value);
                     var expcode = expCodes[expname];
                     queryExpress.queryExpress(expname,expcode, expAppinfo.numSlot.value, object);
                   } else if (expAppinfo.numSlot != null){
                      //用運單編號查詢
                     queryExpress.queryEXPbyNum(expAppinfo.numSlot.value, object);
                   }
                   resetExpInfo(object);
                   break;
                 case OPT_QUERY_NAME:
                   textData = '請提供您的運單編號。';
                   object.setData({
                     expresshead: '',
                     text: textData
                   })
                   break;
                 case OPT_QUERY_NUM_NAME:
                   if (expAppinfo.nameSlot != null && expAppinfo.numSlot != null) { //採用快遞編號+快遞公司方式查詢
                     //獲取快遞公司名稱
                     var expname = getExpCode(expAppinfo.nameSlot.value);
                     var expcode = expCodes[expname];
                     queryExpress.queryExpress(expname,expcode, expAppinfo.numSlot.value, object);
                   }
                   resetExpInfo(object);
                   break;
               }

               break;

             }
           }
         }



    }

   至此,OLAMI API 介面的基本呼叫工作已經完成,至於你要新增語言識別,語法完善,模組新增等就看自己的需求了。

   最後說一下語法檔案.osl下載之後如何匯入。你建立好模組之後,直接選擇上傳OSL檔案即可

優秀自然語言理解部落格文章推薦:

推薦自然語言理解愛好者部落格: