Crea entregas mediante webhook

La entrega de webhook en Dialogflow nos brinda mucho control sobre el flujo de nuestro agente. En este instructivo, necesitas un webhook para validar las secuencias alfanuméricas recopiladas en el intent “Secuencia”. El webhook repetirá en bucle ese intent para recopilar una secuencia larga en iteraciones más administrables.

Crea un webhook con el editor directo

Dialogflow tiene un editor directo en la consola que te permite escribir directamente el código NodeJS, que luego puede implementarse para que se ejecute como un webhook en Firebase.

Para crear un webhook con el editor directo de Dialogflow, sigue estos pasos:

  1. Haz clic en la pestaña Entrega (Fulfillment) en la barra de navegación para ir a la página de entrega.
  2. Activa el botón del editor directo de modo que quede HABILITADO (ENABLED).
  3. Borra el contenido existente en la pestaña package.json del editor directo.
  4. Copia y pega el siguiente contenido JSON en la pestaña package.json del editor directo:

    {
      "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"
      }
    }
    
  5. Borre el código existente en la pestaña index.js del editor directo.

  6. Copia y pega el siguiente código en la pestaña index.js del editor directo:

    /**
     * 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}`);
    }
    
  7. Haz clic en IMPLEMENTAR (DEPLOY).

Ahora deberías poder llamar al agente para probar la integración. Si aún no lo has hecho, sería un buen momento para configurar una de las integraciones por telefonía de un clic de nuestros socios o configurar Dialogflow Phone Gateway para probar tu agente por teléfono.

Comprende el código

Como punto de entrada al webhook, la función dialogflowFirebaseFulfillment se llama cada vez que se activa el webhook. Con cada solicitud, Dialogflow envía el nombre de “acción” que especificaste en la consola de Dialogflow para un intent. El código usa este nombre de acción para determinar a qué función de webhook llamar, handleSequence o validateSequence.

Controlar secuencia

handleSequence es la función principal de este instructivo. Es responsable de todos los aspectos del relleno de ranuras de secuencias, incluidos los siguientes:

  • Reproducir las instrucciones iniciales cuando una sesión ingresa por primera vez al intent.
  • Repetir la secuencia antes de solicitar el siguiente conjunto.
  • Informar a los usuarios finales cómo corregir el bot.
  • Reconocer cuando hay suficientes dígitos para una secuencia válida y de indicar al usuario final cómo finalizar la entrada (consulta “MIN_SEQUENCE_LENGTH” en el código).
  • Repetir indefinidamente el relleno de ranuras para recopilar varias secuencias parciales.
  • Concatenar las secuencias parciales en una secuencia larga.

Validar secuencia

validateSequence es el lugar en el que querrás agregar una conexión a tu almacén de datos para validar la secuencia final y mostrar un mensaje personalizado al usuario en función de esos datos. Si compilas un agente de búsqueda de pedidos, por ejemplo, puedes personalizar la respuesta aquí para que diga lo siguiente:

Thank you. Your order ${verbatim} will arrive on ${lookup.date} and will ${lookup.require_signature ? '' : 'not'} require a signature.

En el ejemplo anterior, lookup es algún objeto que encontraste en tu almacén de datos para este pedido.

Funciones auxiliares

En este ejemplo, no se usa ninguna dependencia específica de Dialogflow. En su lugar, sigue la referencia de WebhookRequest sobre qué esperar en request.body y la referencia de WebhookResponse sobre qué responder con response.json({...}).

El código incluye dos funciones auxiliares para facilitar las siguientes acciones:

  • Envía una respuesta JSON adecuada para la plataforma actual pasando una cadena a sendSSML.
  • Para buscar un contexto de Dialogflow activo desde la solicitud, pasa el nombre de contexto a getOutputContext.

Mejora adicional

Esto te ayudará a comenzar a usar webhook para casos prácticos avanzados. Diseñaste un agente que puede repetir en bucle un mensaje de secuencia mientras un usuario final dice su secuencia, lo que garantiza que el agente virtual los escuche de forma correcta.

Estas son algunas ideas para mejorar aún más la experiencia:

  • Cambia algunas de las respuestas de webhook para que coincidan con tu marca. Por ejemplo, en lugar del mensaje genérico “¿Cuál es tu secuencia?…”, puedes editar el código para decir “¿Cuál es el número de tu pedido? Puedes encontrarla en…” en su lugar.
  • Se recomienda que agregues otro contexto de salida al intent “Secuencia - Listo”. Luego, crea algunos intents nuevos en ese contexto de entrada para permitir que los usuarios hagan preguntas de seguimiento sobre su orden.
  • Si quieres profundizar en este caso práctico, consulta TODO: CHALLENGE en el código de muestra anterior sobre cómo podrías mejorar esta experiencia aún más para tus usuarios.