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 contas, 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 você pode 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, 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 de o Dialogflow ter acesso seguro à sua 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.

    Acessar a página de 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 Próxima. 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 inline
    • 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 o agente

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

  1. Acesse o console do Dialogflow ES.
  2. Selecione o agente predefinido 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 está pronto para ser testado. 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 Seu saldo mais recente: $0,00

Na terceira vez que você interagiu com a conversa, você informou "conta corrente" como o tipo de conta. A intent account.balance.check tem um parâmetro chamado account. Esse parâmetro está definido como "verificando" nesta conversa. A intent também tem um valor de ação de "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 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 vai usar um saldo de conta codificado. Nas próximas etapas, você vai alterar o ambiente da função para que ela extraia 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: