昨天花一天的篇幅在講「問」與「答」的判斷跟回應要怎麼用、怎麼整合在 Bottender 中,但除了問與答以外,其實還有更常見的「意圖」與「實體」的應用方式。
今天要來介紹並試用的是 Google 的 Dialogflow。
在官網上的介紹上看得出來,這個服務很適合串接「Google Assistant/Action on Google」,畢竟是他們自家相關的服務,因此在這個服務中文字、語音是處理的重點。
在 Dialogflow 上,他們使用的概念幾乎都是現在一般處理自然語言理解時的常用概念,包括 Intents(意圖)、 Entity(實體)、Context(情境)還有 Fulfillment(執行要求)。
接下來來簡單介紹一下這些概念:
就是要判斷一句話背後的用意,這跟「問」與「答」的問題分類很像似,舉例來說,下面這句話:
我想預約下禮拜二晚上 8 點的晚餐
背後的意圖就是「預約餐廳」。
就是句子中有提到的一些重要資訊,舉例來說,下面這句話:
我想預約下禮拜二晚上 8 點的晚餐
裡面的實體「日期」就是「下禮拜二」,時間是「晚上 8 點」。
因為人的對話會有上下文,所以有時候一句話根據情境會有不同的意思,例如說了一句「ok」那到底是什麼東西 ok?必須要看情面的情境。
這個部分可以用 Bottender 來處理,也可以讓 Dialogflow 處理一部分,比較複雜今天就先不講。
Dialogflow 上能編輯的回應非常有限,主要是靜態文字回應,但很多時候,其實必須要執行一些程式或是查詢資料庫,這時候就必須要用 Fulfillment,你可以在它的介面上寫程式部署到「Cloud Functions for Firebase」或是讓它打 HTTP API 到 Webhook。
這兩個方式我都不喜歡,所以基本上我不會去使用 Fulfillment,我還是會希望我的程式擁有主控權,而且能好好的進行版控跟測試,所以不管是在介面上寫程式還是去接這個 Webhook 都是把事情變複雜。我基本上還是推薦用程式去呼叫 Dialogflow API。
從 Dialogflow 的頁面登入後,我們就可以前往 Dialogflow Console:
(這邊有點小尷尬是 Dialogflow 已經登入一次了,為什麼進 Dialogflow Console 還要再 Sign in....)
如果沒有使用過,應該近來會看到沒有 Agent 的畫面:
這時候就點「Create Agent」來建立 Agent:
預設語系選「zh-tw」,而時區沒看到「Asia/Taipei」所以選了一樣是 +8 時區的香港。
過了大約 30 秒,新的 Agent 就建好了:
(注意:在機器人或是 Bottender App 裡面不一定只能接一個 Agent,也是有依照情況使用不同 Agent 的可能)
裡面預設會有兩個意圖,Welcome Intent 跟 Fallback Intent,Welcome Intent(歡迎意圖)通常是當使用者第一次互動時觸發、Fallback Intent 則是當判斷不出意圖時觸發。
接著我們要來建立一個新意圖,按下「Create Intent」就會進入這個畫面:
接著把意圖名稱填一下,加一下訓練句,在訓練句上標記一下實體。反白就可以標記:
@sys.
開頭的表示是 Dialogflow 內建的實體類別。這邊直接採用上面那個訂餐的範例:
這邊另一個重點是要設定一下 Action(動作) 跟 Parameter(參數),因為打 API 時會需要這幾個變數。其他 Context、Event、Response、Fulfillment 等等的我們都不需要用,所以不用動它。
時間太趕了來不及加很多訓練句,雖然我想兩句應該是有點少...。
建完可以直接在右邊「Try it now」的地方測試一下句子:
除了上面的步驟外,我們需要到 - 「快速入門導覽課程:使用 API 與代理程式互動
」這頁的,「設定 GCP 專案和驗證
」這個部分來下載私密金鑰的 JSON 檔,先同意一下條款後就可以下載檔案囉:
載完還不是結束,還必須把 JSON 的路徑放到環境變數:
// .env
GOOGLE_APPLICATION_CREDENTIALS=你的 JSON 檔案路徑
這樣接下來我們寫程式時,Dialogflow SDK 才能讀到這個 JSON 檔。
因為 Dialog 有自己的 Node SDK,所以我們可以直接安裝來用
const dialogflow = require('dialogflow'); // 記得安裝
const { format } = require('date-fns'); // 記得安裝
// 這個要填自己的
const PROJECT_ID = 'YOUR-PROJECT_ID';
const sessionClient = new dialogflow.SessionsClient();
module.exports = async function App(context) {
if (context.event.isText) {
const sessionPath = sessionClient.sessionPath(
PROJECT_ID,
context.session.id
);
const request = {
session: sessionPath,
queryInput: {
text: {
text: context.event.text,
languageCode: 'zh-tw',
},
},
queryParams: {
timeZone: 'Asia/Taipei',
},
};
// 偵測意圖
const responses = await sessionClient.detectIntent(request);
const { intent, parameters } = responses[0].queryResult;
if (intent.displayName === '預約餐廳') {
const { fields } = parameters;
const date =
fields.date && fields.date.stringValue
? new Date(fields.date.stringValue)
: new Date();
const time =
fields.time && fields.time.stringValue
? new Date(fields.time.stringValue)
: new Date();
await context.sendText(
`你想預約的是:${format(date, 'yyyy-MM-dd')} ${format(time, 'p')}`
);
} else {
await context.sendText('聽不懂喔');
}
}
};
今天時間有點趕,草率的試試看,好像還 ok 喔:
之後有空會來提供一下更好的整合方式,這還有點小粗糙。
雖然「意圖」與「實體」應用起來比「問」與「答」更靈活,但要去設計、思考有哪些「意圖」與「實體」並撰寫例句並不是一件簡單的事,而且需要持續地維護以及補強。要怎麼要去使用這些服務沒有唯一的答案,更多時候是要用混合式的解法,看何種方式跟你的程式去結合最合理順暢。Dialogflow 在很多概念上的設計都不錯,但由於它是跟 Google Assistant 做整合的,主要還是在處理文字跟語音上最適合,看起來雖然可以處理比較複雜的互動但那部分就沒有那麼好用了。