Criar um serviço de webhook

O agente pré-criado que você criou na última etapa não pode fornecer dados dinâmicos, como saldos de contas, porque tudo está fixado no código no agente. Nesta etapa do tutorial, você criará um webhook que pode fornecer dados dinâmicos ao agente. As funções do Cloud são usadas para hospedar o webhook neste tutorial devido à simplicidade dele, mas há muitas outras maneiras de hospedar um serviço de webhook. No exemplo, também usamos a linguagem de programação Go, mas é possível usar qualquer linguagem compatível com o Cloud Functions.

Criar a função

O Cloud Functions pode ser criado com o console do Google Cloud. Acesse a documentação e abra o console. Para criar uma função para este tutorial:

  1. É importante que o agente e a função do Dialogflow estejam no mesmo projeto. Essa é a maneira mais fácil de o Dialogflow ter acesso seguro à função. Antes de criar a função, selecione seu projeto no console do Google Cloud.

    Acessar o seletor de projetos

  2. Abra a página de visão geral do Cloud Functions.

    Acessar a visão geral do Cloud Functions

  3. Clique em Criar função e defina os seguintes campos:

    • Ambiente: 1a geração
    • Nome da função: tutorial-banking-webhook
    • Região: se você especificou uma região para seu agente, use a mesma região.
    • Tipo de acionador HTTP: HTTP
    • URL: clique no botão de cópia aqui e salve o valor. Você precisará desse URL ao configurar o webhook.
    • Autenticação: exige autenticação.
    • Exigir HTTPS: marcada
  4. Clique em Save.

  5. Clique em Next. Você não precisa de configurações especiais de ambiente de execução, build, conexões ou segurança.

  6. Defina os seguintes campos:

    • Ambiente de execução: selecione o ambiente de execução mais recente do Go.
    • Código-fonte: editor in-line
    • Ponto de entrada: HandleWebhookRequest
  7. Substitua o código pelo seguinte:

    package estwh
    
    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    	"strings"
    
    	"cloud.google.com/go/spanner"
      "google.golang.org/grpc/codes"
    )
    
    // client is a Spanner client, created only once to avoid creation
    // for every request.
    // See: https://cloud.google.com/functions/docs/concepts/go-runtime#one-time_initialization
    var client *spanner.Client
    
    func init() {
    	// If using a database, these environment variables will be set.
    	pid := os.Getenv("PROJECT_ID")
    	iid := os.Getenv("SPANNER_INSTANCE_ID")
    	did := os.Getenv("SPANNER_DATABASE_ID")
    	if pid != "" && iid != "" && did != "" {
    		db := fmt.Sprintf("projects/%s/instances/%s/databases/%s",
    			pid, iid, did)
    		log.Printf("Creating Spanner client for %s", db)
    		var err error
    		// Use the background context when creating the client,
    		// but use the request context for calls to the client.
    		// See: https://cloud.google.com/functions/docs/concepts/go-runtime#contextcontext
    		client, err = spanner.NewClient(context.Background(), db)
    		if err != nil {
    			log.Fatalf("spanner.NewClient: %v", err)
    		}
    	}
    }
    
    type queryResult struct {
    	Action     string                 `json:"action"`
    	Parameters map[string]interface{} `json:"parameters"`
    }
    
    type text struct {
    	Text []string `json:"text"`
    }
    
    type message struct {
    	Text text `json:"text"`
    }
    
    // webhookRequest is used to unmarshal a WebhookRequest JSON object. Note that
    // not all members need to be defined--just those that you need to process.
    // As an alternative, you could use the types provided by
    // the Dialogflow protocol buffers:
    // https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookRequest
    type webhookRequest struct {
    	Session     string      `json:"session"`
    	ResponseID  string      `json:"responseId"`
    	QueryResult queryResult `json:"queryResult"`
    }
    
    // webhookResponse is used to marshal a WebhookResponse JSON object. Note that
    // not all members need to be defined--just those that you need to process.
    // As an alternative, you could use the types provided by
    // the Dialogflow protocol buffers:
    // https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookResponse
    type webhookResponse struct {
    	FulfillmentMessages []message `json:"fulfillmentMessages"`
    }
    
    // accountBalanceCheck handles the similar named action
    func accountBalanceCheck(ctx context.Context, request webhookRequest) (
    	webhookResponse, error) {
    	account := request.QueryResult.Parameters["account"].(string)
    	account = strings.ToLower(account)
    	var table string
    	if account == "savings account" {
    		table = "Savings"
    	} else {
    		table = "Checking"
    	}
    	s := "Your balance is $0"
    	if client != nil {
    		// A Spanner client exists, so access the database.
    		// See: https://pkg.go.dev/cloud.google.com/go/spanner#ReadOnlyTransaction.ReadRow
    		row, err := client.Single().ReadRow(ctx,
    			table,
    			spanner.Key{1}, // The account ID
    			[]string{"Balance"})
    		if err != nil {
    			if spanner.ErrCode(err) == codes.NotFound {
    				log.Printf("Account %d not found", 1)
    			} else {
    				return webhookResponse{}, err
    			}
    		} else {
    			// A row was returned, so check the value
    			var balance int64
    			err := row.Column(0, &balance)
    			if err != nil {
    				return webhookResponse{}, err
    			}
    			s = fmt.Sprintf("Your balance is $%.2f", float64(balance)/100.0)
    		}
    	}
    	response := webhookResponse{
    		FulfillmentMessages: []message{
    			{
    				Text: text{
    					Text: []string{s},
    				},
    			},
    		},
    	}
    	return response, nil
    }
    
    // Define a type for handler functions.
    type handlerFn func(ctx context.Context, request webhookRequest) (
    	webhookResponse, error)
    
    // Create a map from action to handler function.
    var handlers map[string]handlerFn = map[string]handlerFn{
    	"account.balance.check": accountBalanceCheck,
    }
    
    // handleError handles internal errors.
    func handleError(w http.ResponseWriter, err error) {
    	log.Printf("ERROR: %v", err)
    	http.Error(w,
    		fmt.Sprintf("ERROR: %v", err),
    		http.StatusInternalServerError)
    }
    
    // HandleWebhookRequest handles WebhookRequest and sends the WebhookResponse.
    func HandleWebhookRequest(w http.ResponseWriter, r *http.Request) {
    	var request webhookRequest
    	var response webhookResponse
    	var err error
    
    	// Read input JSON
    	if err = json.NewDecoder(r.Body).Decode(&request); err != nil {
    		handleError(w, err)
    		return
    	}
    	log.Printf("Request: %+v", request)
    
    	// Get the action from the request, and call the corresponding
    	// function that handles that action.
    	action := request.QueryResult.Action
    	if fn, ok := handlers[action]; ok {
    		response, err = fn(r.Context(), request)
    	} else {
    		err = fmt.Errorf("Unknown action: %s", action)
    	}
    	if err != nil {
    		handleError(w, err)
    		return
    	}
    	log.Printf("Response: %+v", response)
    
    	// Send response
    	if err = json.NewEncoder(w).Encode(&response); err != nil {
    		handleError(w, err)
    		return
    	}
    }
    
    

  8. Clique em Implantar.

  9. Aguarde até que o indicador de status mostre que a função foi implantada com sucesso. Enquanto espera, examine o código que você acabou de implantar.

Configurar o webhook do agente

Agora que o webhook existe como um serviço, você precisa associá-lo ao seu agente. Isso é feito por meio do fulfillment. Para ativar e gerenciar o fulfillment do seu agente:

  1. Acesse o Console do Dialogflow ES.
  2. Selecione o agente pré-criado que você acabou de criar.
  3. Selecione Fulfillment no menu da barra lateral esquerda.
  4. Alterne o campo Webhook para Ativado.
  5. Informe o URL que você copiou acima. Não preencha os outros campos.
  6. Clique em Salvar, na parte inferior da página.

Captura de tela da ativação do fulfillment.

Agora que o fulfillment está ativado para o agente, é necessário ativá-lo para uma intent:

  1. Selecione Intents no menu da barra lateral à esquerda.
  2. Selecione a intent account.balance.check.
  3. Role para baixo até a seção Fulfillment.
  4. Alternar Ativar chamada de webhook para este intent para ativada.
  5. Clique em Save.

Testar o agente

Seu agente está pronto para tentar. Clique no botão Testar agente para abrir o simulador. Tente ter a seguinte conversa com o agente:

Turno da conversa Você Agente
1 Olá Olá, agradecemos por escolher o ACME Bank.
2 Quero saber o saldo da minha conta Você quer saldo para qual conta: poupança ou corrente?
3 Verificando Aqui está seu saldo mais recente: $0,00

Na terceira rodada de conversas, você forneceu "checking" como tipo de conta. A intent account.balance.check tem um parâmetro chamado account. Esse parâmetro está definido como "checking" nesta conversa. A intent também tem um valor de ação "account.balance.check". O serviço de webhook é chamado e recebe os valores de parâmetro e ação.

Se você examinar o código do webhook acima, verá que essa ação aciona uma função nomeada semelhante a ser chamada. A função determina o saldo da conta. A função verifica se variáveis de ambiente específicas estão definidas com informações para se conectar ao banco de dados. Se essas variáveis de ambiente não forem definidas, a função usará um saldo de conta fixado no código. Nas próximas etapas, você vai alterar o ambiente da função para que ela recupere dados de um banco de dados.

Solução de problemas

O código do webhook inclui instruções de geração de registros. Se você tiver problemas, tente visualizar os registros da sua função.

Mais informações

Para mais informações sobre as etapas acima, consulte: