Crie o processamento de pedidos através de webhook

O preenchimento de webhooks no Dialogflow dá-nos muito controlo sobre o fluxo do nosso agente. Neste tutorial, precisa de um webhook para validar as sequências alfanuméricas recolhidas na intenção "Sequence". O webhook vai repetir esse intento para recolher uma sequência longa em iterações mais fáceis de gerir.

Crie um webhook com o editor inline

O Dialogflow tem um editor inline na consola que lhe permite escrever diretamente código NodeJS, que pode ser implementado para ser executado como um webhook nas Cloud Functions.

Para criar um webhook através do editor inline do Dialogflow, siga estes passos:

  1. Clique no separador Preenchimento na barra de navegação para aceder à página de preenchimento.
  2. Ative o botão do editor inline.
  3. Elimine o conteúdo existente no separador package.json do editor inline.
  4. Copie e cole o conteúdo JSON abaixo no separador package.json do editor inline:

    {
      "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. Elimine o código existente no separador index.js do editor inline.

  6. Copie e cole o código abaixo no separador index.js do editor inline:

    /**
     * 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. Clique em IMPLEMENTAR.

Já deve conseguir testar a integração chamando o agente. Se ainda não o fez, agora é um bom momento para configurar uma das integrações de telefonia com um clique dos nossos parceiros ou configurar o Dialogflow Phone Gateway para testar o seu agente por telefone.

Compreender o código

Como ponto de entrada para o webhook, a função dialogflowFirebaseFulfillment aqui é chamada sempre que o webhook é acionado. Com cada pedido, o Dialogflow envia o nome da "ação" que especificou na consola do Dialogflow para um objetivo. O código usa este nome de ação para determinar que função de webhook chamar: handleSequence ou validateSequence.

Sequência de identificadores

handleSequence é a função essencial deste tutorial. É responsável por todos os aspetos do preenchimento de espaços da sequência, incluindo:

  • Dizer as instruções iniciais quando uma sessão entra pela primeira vez na intenção.
  • Repetir a sequência antes de pedir o conjunto seguinte.
  • Indicar aos utilizadores finais como corrigir o bot.
  • Reconhecer quando existem dígitos suficientes para uma sequência válida e indicar ao utilizador final como finalizar a entrada (consulte `MIN_SEQUENCE_LENGTH` no código).
  • Repetir o preenchimento de espaços para recolher várias sequências parciais.
  • Concatenar as sequências parciais numa sequência longa.

Validar sequência

validateSequence é onde deve adicionar uma ligação ao seu repositório de dados para validar a sequência final e devolver uma mensagem personalizada ao utilizador com base nesses dados. Se estiver a criar um agente de pesquisa de encomendas, por exemplo, pode personalizar a resposta aqui para dizer:

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

Onde lookup é algum objeto que encontrou no seu repositório de dados para esta encomenda.

Funções auxiliares

Este exemplo não usa dependências específicas do Dialogflow. Em alternativa, siga a referência WebhookRequest para saber o que esperar em request.body e a referência WebhookResponse para saber com o que responder em response.json({...}).

O código inclui duas funções auxiliares para facilitar o seguinte:

  • Envie o JSON de resposta adequado para a plataforma atual transmitindo uma string para sendSSML.
  • Procure um contexto do Dialogflow ativo no pedido passando o nome do contexto para getOutputContext.

Melhoria adicional

Isto deve ajudar a começar a usar o webhook para exemplos de utilização avançados. Criou um agente que pode repetir um comando de sequência enquanto um utilizador final pronuncia a sua sequência, garantindo ao longo do processo que o agente virtual está a ouvi-lo corretamente.

Seguem-se algumas ideias para melhorar ainda mais a experiência:

  • Alterar algumas das respostas do webhook para corresponderem à sua marca. Por exemplo, em vez do comando genérico "Qual é a sua sequência?...", pode editar o código para dizer "Qual é o seu número de encomenda? Em alternativa, pode encontrar o conteúdo em …".
  • Considere adicionar outro contexto de saída à intenção "Sequence - Done" e, em seguida, criar algumas novas intenções nesse contexto de entrada para permitir que os utilizadores façam perguntas de seguimento sobre a respetiva encomenda.
  • Se quiser analisar mais detalhadamente este exemplo de utilização, consulte o TODO: CHALLENGE no exemplo de código acima para saber como pode melhorar ainda mais esta experiência para os seus utilizadores.