Dialogflow 中的 Webhook 執行要求可讓我們大幅控管代理程式流程。在本教學課程中,您需要使用 Webhook 驗證「Sequence」意圖中收集的英數字元序列。Webhook 會重複執行該意圖,以更易於管理的方式收集長序列。
使用內嵌編輯器建立 Webhook
Dialogflow 主控台內建內嵌編輯器,可讓您直接編寫 NodeJS 程式碼,然後部署至 Cloud Functions,以 Webhook 形式執行。
如要使用 Dialogflow 的內嵌編輯器建立 Webhook,請按照下列步驟操作:
- 按一下導覽列上的「Fulfillment」(執行要求) 分頁標籤,前往執行要求頁面。

- 將內嵌編輯器的按鈕切換為「已啟用」。

- 刪除內嵌編輯器
package.json分頁中的現有內容。
複製下列 JSON 內容,然後貼到內嵌編輯器的
package.json分頁中:{ "name": "DialogflowFirebaseWebhook", "description": "Firebase Webhook dependencies for a Dialogflow agent.", "version": "0.0.1", "private": true, "license": "Apache Version 2.0", "author": "Google Inc.", "engines": { "node": "10" }, "scripts": { "lint": "semistandard --fix \"**/*.js\"", "start": "firebase deploy --only functions", "deploy": "firebase deploy --only functions" }, "dependencies": { "firebase-functions": "^2.0.2", "firebase-admin": "^5.13.1" } }刪除內嵌編輯器
index.js分頁中的現有程式碼。
複製下列程式碼,然後貼到內嵌編輯器的「
index.js」分頁:/** * Copyright 2020 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; const functions = require('firebase-functions'); // TODO: set this to the minimum valid length for your sequence. // There's no logic in here to enforce this length, but once the // user has said this many digits, the slot-filling prompt will // also instruct the user to say "that's all" to end the slot-filling. const MIN_SEQUENCE_LENGTH = 10; exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { let dfRequest = request.body; let action = dfRequest.queryResult.action; switch (action) { case 'handle-sequence': handleSequence(dfRequest, response); break; case 'validate-sequence': validateSequence(dfRequest, response); break; default: response.json({ fulfillmentText: `Webhook for action "${action}" not implemented.` }); } }); //// // Helper functions /* Send an SSML response. * @param request: Dialogflow WebhookRequest JSON with camelCase keys. * See https://cloud.google.com/dialogflow/es/docs/reference/common-types#webhookrequest * @param response: Express JS response object * @param ssml: SSML string. * @example: sendSSML(request, response, 'hello') * Will call response.json() with SSML payload '<speak>hello</speak>' */ function sendSSML(request, response, ssml) { ssml = `<speak>${ssml}</speak>`; if (request.originalDetectIntentRequest.source == 'GOOGLE_TELEPHONY') { // Dialogflow Phone Gateway Response // see https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2beta1#google.cloud.dialogflow.v2beta1.Intent.Message.TelephonySynthesizeSpeech response.json({ fulfillmentMessages: [{ platform: 'TELEPHONY', telephonySynthesizeSpeech: {ssml: ssml} }] }); } else { // Some CCAI telephony partners accept SSML in a plain text response. // Check your specific integration and customize the payload here. response.json({ fulfillmentText: ssml }); } } /* Extract an output context from the incoming WebhookRequest. * @param request: Dialogflow WebhookRequest JSON with camelCase keys. * See https://cloud.google.com/dialogflow/es/docs/reference/common-types#webhookrequest * @param name: A string * @return: The context object if found, or undefined * @see: https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2#google.cloud.dialogflow.v2.Context * and note this webhook uses JSON camelCase instead of RPC snake_case. * @example: * // Modify an existing output content * let context = getOutputContext(request, 'some-context'); * context.lifespanCount = 5; * context.parameters.some_parameter = 'new value'; * response.json({ * fulfillmentText: 'new value set', * outputContexts: [context] * }); */ function getOutputContext(request, name) { return request.queryResult.outputContexts.find( context => context.name.endsWith(`/contexts/${name}`) ); } //// // Action handler functions /* * Fulfillment function for: * actions: handle-sequence * intents: "Sequence", "Sequence - Edit" * @param request: Dialogflow WebhookRequest JSON with camelCase keys. * See https://cloud.google.com/dialogflow/es/docs/reference/common-types#webhookrequest * @param response: Express JS response object */ function handleSequence(request, response) { let parameters = request.queryResult.parameters; let isSlotFilling = !request.queryResult.allRequiredParamsPresent; let isEditing = getOutputContext(request, 'editing-sequence'); console.log(request.queryResult.action + ': ' + JSON.stringify(parameters)); if (isSlotFilling) { // Prompt the user for the sequence let verbatim = `<prosody rate="slow"><say-as interpret-as="verbatim">${parameters.existing_sequence}</say-as></prosody>`; if (!parameters.existing_sequence && !parameters.new_sequence) { // Initial prompt response.json({ fulfillmentText: "What is your sequence? Please pause after a few characters so I can confirm as we go." }); } else if (!isEditing) { // Confirm what the system heard with the user. We customize the response // according to how many sequences we've heard to make the prompts less // verbose. if (!parameters.previous_sequence) { // after the first input sendSSML(request, response, `Say "no" to correct me at any time. Otherwise, what comes after ${verbatim}`); } else if (parameters.existing_sequence.length < MIN_SEQUENCE_LENGTH) { // we know there are more characters to go sendSSML(request, response, `${verbatim} What's next?`); } else { // we might have all we need sendSSML(request, response, `${verbatim} What's next? Or say "that's all".`); } } else { // User just said "no" sendSSML(request, response, `Let's try again. What comes after ${verbatim}`); } } else { // Slot filling is complete. // Construct the full sequence. let sequence = (parameters.existing_sequence || '') + (parameters.new_sequence || ''); // Trigger the follow up event to get back into slot filling for the // next sequence. response.json({ followupEventInput: { name: 'continue-sequence', parameters: { existing_sequence: sequence, previous_sequence: parameters.existing_sequence || '' } } }); // TODO: CHALLENGE: consider validating the sequence here. // The user has already confirmed existing_sequence, so if you find a unique // record in your database with this existing_sequence prefix, you could send // a followUpEventInput like 'validated-sequence' to skip to the next part // of the flow. You could either create a new intent for this event, or // reuse the "Sequence - done" intent. If you reuse the "done" intent, you // could add another parameter "assumed_sequence" with value // "#validated-sequence.sequence", then modify the validateSequence function // below to customize the response for this case. } } /* * Fulfillment function for: * action: validate-sequence * intents: "Sequence - Done" * @param request: Dialogflow WebhookRequest JSON with camelCase keys. * See https://cloud.google.com/dialogflow/es/docs/reference/common-types#webhookrequest * @param response: Express JS response object */ function validateSequence(request, response) { let parameters = request.queryResult.parameters; // TODO: add logic to validate the sequence and customize your response let verbatim = `<say-as interpret-as="verbatim">${parameters.sequence}</say-as>`; sendSSML(request, response, `Thank you. Your sequence is ${verbatim}`); }按一下「部署」。
現在您應該可以呼叫代理程式,測試整合狀況。如果尚未設定,現在是透過合作夥伴設定一鍵電話整合服務的好時機,或是設定 Dialogflow Phone Gateway,透過電話測試代理程式。
瞭解程式碼
做為 Webhook 的進入點,每次觸發 Webhook 時,系統都會呼叫此處的 dialogflowFirebaseFulfillment 函式。每次提出要求時,Dialogflow 都會傳送您在 Dialogflow 主控台中為意圖指定的「動作」名稱。程式碼會使用這個動作名稱,判斷要呼叫哪個 Webhook 函式:handleSequence 或 validateSequence。
處理序列
handleSequence 是本教學課程的核心函式。負責序列填空的所有環節,包括:
- 工作階段首次進入意圖時,說出初始指令。
- 在提示下一組之前,先重複序列。
- 告知使用者如何修正機器人。
- 辨識何時有足夠的數字可組成有效序列,並告知使用者如何完成輸入 (請參閱程式碼中的 `MIN_SEQUENCE_LENGTH`)。
- 循環填寫空位,收集多個部分序列。
- 將部分序列串連成一個長序列。
驗證序列
validateSequence 是您要新增資料儲存庫連線的位置,以便驗證最終序列,並根據該資料向使用者傳回自訂訊息。舉例來說,如果您要建構訂單查詢代理程式,可以在這裡自訂回覆內容,例如:
Thank you. Your order ${verbatim} will arrive on ${lookup.date} and will
${lookup.require_signature ? '' : 'not'} require a signature.
其中 lookup 是您在該訂單的資料儲存庫中找到的物件。
輔助函式
這個範例不會使用任何 Dialogflow 專用的依附元件。請改為參閱 WebhookRequest 參考資料,瞭解 request.body 中預期的內容,並參閱 WebhookResponse 參考資料,瞭解 response.json({...}) 中應回覆的內容。
程式碼包含兩個輔助函式,可簡化下列作業:
- 將字串傳遞至
sendSSML,為目前平台傳送適當的回應 JSON。 - 將背景資訊名稱傳遞至
getOutputContext,從要求中尋找有效的 Dialogflow 背景資訊。
進一步改善
這應該有助於您開始使用 Webhook 處理進階用途。您設計的代理程式可以在使用者說出序列時,循環提示序列,並在過程中確保虛擬代理能正確聽到使用者的聲音。
以下提供幾項建議,進一步提升使用體驗:
- 變更部分 Webhook 回覆,使其符合品牌風格。舉例來說,您可以編輯程式碼,將一般「你的序號是什麼?...」提示改為「你的訂單編號是什麼?請改為使用「你可以在 ... 找到」。
- 考慮在「Sequence - Done」意圖中新增另一個輸出背景資訊,然後在該輸入背景資訊下建立一些新意圖,讓使用者詢問訂單的後續問題。
- 如要進一步深入瞭解這個用途,請查看上述程式碼範例中的
TODO: CHALLENGE,瞭解如何為使用者提供更優質的體驗。