Dialogflow 中的网络钩子 fulfilment 使我们能够更好地控制代理流。在本教程中,您需要一个网络钩子来验证在“序列”意图中收集的字母数字序列。该网络钩子会反复轮询该意图,以便在更易于管理的迭代中收集长序列。
使用内嵌编辑器创建网络钩子
Dialogflow 在控制台中有一个内嵌编辑器,使您可以直接写入 NodeJS 代码,然后将其部署作为网络钩子在 Cloud Functions 上运行。
如需使用 Dialogflow 的内嵌编辑器创建网络钩子,请按以下步骤操作:
- 点击导航栏上的 Fulfillment 标签页,转到“Fulfillment”页面。
- 将内嵌编辑器的按钮切换为“已启用”(ENABLED)。
- 删除内嵌编辑器的
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}`); }
点击部署 (DEPLOY)。
现在,您应该可以通过调用代理来测试集成了。如果您尚未执行此操作,建议您立即设置我们的合作伙伴提供的一键式电话集成,或设置 Dialogflow Phone Gateway 来通过电话测试代理。
了解代码
作为网络钩子的入口点,每次触发网络钩子时,都会调用此处的 dialogflowFirebaseFulfillment
函数。对于每个请求,Dialogflow 都会发送您在 Dialogflow 控制台中为意图指定的“操作”名称。代码会使用此操作名称来确定要调用的网络钩子函数(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 上下文。
进一步改进
这可以帮助您开始使用网络钩子来实现高级使用场景。您设计了一个代理,该代理可在最终用户说出其序列时循环显示序列提示,从而确保虚拟代理能够正确地听到它们。
以下是一些进一步改进体验的方法:
- 更改一些网络钩子响应,以与您的品牌相符。例如,您可以修改代码,指定“您的订单号是什么?”,而不是宽泛的“您的序列是什么?”您可以改为在...”上查找。
- 考虑向“序列 - 完成”意图添加其他输出上下文,然后在该输入上下文下创建一些新意图,以便用户可以就其订单继续提问。
- 如果您想深入了解此使用场景,请查看上面示例代码中的
TODO: CHALLENGE
,了解如何进一步改进用户体验。