Criar um serviço de webhook

O agente predefinido que você criou na última etapa não pode fornecer dados dinâmicos, como saldos de conta, porque tudo está codificado no agente. Nesta etapa do tutorial, você vai criar um webhook que pode fornecer dados dinâmicos ao agente. As funções do Cloud Run são usadas para hospedar o webhook neste tutorial devido à simplicidade, mas há muitas outras maneiras de hospedar um serviço de webhook. O exemplo também usa a linguagem de programação Go, mas é possível usar qualquer linguagem compatível com as funções do Cloud Run.

Criar a função

As funções do Cloud Run podem ser criadas 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 do Dialogflow e a função estejam no mesmo projeto. Essa é a maneira mais fácil do 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 das funções do Cloud Run.

    Acesse a visão geral das funções do Cloud Run

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

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

  5. Clique em Next (você não precisa de recursos especiais de ambiente de execução, build ou configurações de 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 por este:

    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. Enquanto aguarda, examine o código que você acabou de implantar.

Configurar o webhook para seu agente

Agora que o webhook existe como um serviço, é necessário associá-lo ao seu agente. Isso é feito por meio do fulfillment. Para ativar e gerenciar o fulfillment do 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 ativar o fulfillment 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 Salvar.

Testar o agente

Seu agente já pode fazer um teste. Clique no botão Testar agente para abrir o simulador. Tente ter a seguinte conversa com o agente:

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

Na terceira vez que você interagiu com o bot, você informou "checking" como o tipo de conta. A intent account.balance.check tem um parâmetro chamado account. Esse parâmetro é definido como "em verificação", 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, vai notar que essa ação aciona uma função com nome semelhante para ser chamada. A função determina o saldo da conta. A função verifica se as 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 estiverem definidas, a função usa um saldo de conta fixado no código. Nas próximas etapas, você vai alterar o ambiente da função para recuperar dados de um banco de dados.

Solução de problemas

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

Mais informações

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