Como migrar do Dialogflow ES para o CX

Os agentes do Dialogflow CX fornecem controles de conversa e ferramentas de conversa mais eficientes que os agentes do Dialogflow ES. Se o agente do Dialogflow ES lida com conversas complexas, considere migrar para o Dialogflow CX.

Neste guia, descrevemos como migrar um agente do Dialogflow ES para o Dialogflow CX. Esses dois tipos de agente têm muitas diferenças fundamentais. Portanto, não há uma maneira direta de realizar essa migração.

Se você usa este guia para fazer uma migração, clique no botão Enviar feedback acima para dar um feedback positivo ou negativo. Usaremos esse feedback para melhorar o guia.

Em alto nível, o recomendado é um processo híbrido automatizado/manual. Você usará uma ferramenta que lê alguns dos dados do agente do Dialogflow ES, grava esses dados no agente do Dialogflow CX e captura uma lista de tarefas. Em seguida, recria seu agente CX completo usando as práticas recomendadas, a lista de tarefas e os dados migrados pela ferramenta.

Noções básicas do Dialogflow CX

Antes de tentar essa migração, você precisa ter um bom entendimento de como o Dialogflow CX funciona. Você pode começar aqui:

  1. Princípios básicos
  2. Vídeos de introdução
  3. Guias de início rápido

Leia também outros documentos conceituais que tenham recursos que você provavelmente vai precisar no novo agente. Concentre-se no seguinte:

Entenda as diferenças de ES/CX

Nesta seção, listamos as diferenças mais importantes entre o Dialogflow ES e o CX. Ao executar as etapas de migração manual mais tarde, consulte esta seção para orientação.

Controle de caminho da conversa e estrutura

O ES fornece o seguinte para controle de caminho de conversa e estrutura:

  • Intents são usadas como elementos básicos do agente. Em qualquer ponto da conversa, é feita a correspondência com uma intent, e, de certa forma, cada intent é um nó da conversa.
  • O contexto é usado para controlar a conversa. O contexto é usado para controlar quais intents podem ser correspondidas a qualquer momento. O contexto expira após um certo número de rodadas de conversa. Portanto, esse tipo de controle pode ser impreciso para conversas longas.

A CX fornece uma hierarquia de recursos de estrutura e controles mais precisos sobre o caminho da conversa:

  • Páginas são nós gráficos para a conversa. As conversas de CX são semelhantes às máquinas de estado. Em qualquer ponto da conversa, uma página fica ativa. Com base na entrada ou nos eventos do usuário final, a conversa pode fazer a transição para outra página. É comum que uma página permaneça ativa por várias interações.
  • Fluxos são grupos de páginas relacionadas. Cada fluxo deve lidar com um tópico de conversa de alto nível.
  • Os gerenciadores de estado são usados para controlar transições e respostas. Há três tipos de gerenciadores de estado:
    • Rota da intent: contém uma intent que precisa ser correspondida, respostas opcionais e transição de página opcional.
    • Rota de condição: contém uma condição que precisa ser atendida, respostas opcionais e transição de página opcional.
    • Manipulador de eventos: contém um nome de evento que precisa ser invocado, respostas opcionais e transição de página opcional.
  • Escopo é usado para controlar se um gerenciador de estado pode ser chamado. A maioria dos gerenciadores está associada a uma página ou a todo o fluxo. Se a página ou o fluxo associado estiver ativo, o gerenciador estará no escopo e poderá ser chamado. Uma rota de intent CX no escopo é semelhante a uma intent ES com um contexto de entrada ativo.

Ao projetar os fluxos e as páginas do seu agente, entenda as orientações da seção de fluxo do guia de design do agente.

Preenchimento de formulário

O ES usa o preenchimento de slots para coletar os parâmetros obrigatórios do usuário final:

  • Esses parâmetros são de intents marcados como obrigatórios.
  • A intent continua sendo correspondida até que todos os parâmetros necessários sejam coletados.
  • É possível definir um prompt pedindo ao usuário final para fornecer um valor.

O CX usa o preenchimento de formulário para coletar os parâmetros obrigatórios do usuário final:

  • Esses parâmetros são associados a uma página e coletados enquanto ela está ativa.
  • Use as rotas de condições das páginas para determinar se o preenchimento do formulário foi concluído. Essas rotas de condições normalmente fazem a transição para outra página.
  • É possível definir um prompt e gerenciadores de nova solicitação para processar várias tentativas de coletar um valor.

Transições

O ES faz a transição automática de uma intent para outra quando a entrada do usuário final corresponde a uma intent. Essa correspondência só pode ocorrer para intents que não têm contexto de entrada ou que tenham um contexto de entrada ativo.

A CX faz a transição de uma página para a próxima quando um gerenciador de estado no escopo atende aos requisitos e fornece um destino de transição. Com essas transições, é possível orientar os usuários finais de forma confiável durante as conversas. Há várias maneiras de controlar essas transições:

  • A correspondência de intent pode acionar uma rota de intent.
  • Satisfazer uma condição pode acionar uma rota de condição.
  • A invocação de um evento pode acionar um manipulador de eventos.
  • Os gerenciadores de nova solicitação podem causar uma transição quando o usuário final não fornecer um valor após várias tentativas.
  • Você pode usar destinos de transição simbólica para destinos de transição.

Respostas do agente

As respostas do agente ES são enviadas ao usuário final quando há correspondência com uma intent:

  • O agente pode selecionar uma mensagem para a resposta em uma lista de possíveis respostas.
  • As respostas podem ser específicas da plataforma, que podem usar formatos de resposta avançados.
  • As respostas podem ser orientadas por webhooks.

As respostas do agente CX são enviadas ao usuário final quando o fulfillment é chamado. Ao contrário do fulfillment do ES, que sempre envolve um webhook, o fulfillment do CX pode ou não envolver a chamada de um webhook, dependendo se o recurso de fulfillment tem um webhook configurado. Tanto as respostas estáticas quanto as dinâmicas com base em respostas do webhook são controladas pelo fulfillment. Há várias maneiras de criar respostas de agentes:

  • O fulfillment pode ser fornecido a qualquer tipo de gerenciador de estado.
  • Várias respostas podem ser concatenadas durante uma rodada de conversas por meio da fila de resposta. Em alguns casos, esse recurso pode simplificar o design do agente.
  • A CX não oferece suporte a respostas integradas específicas da plataforma. No entanto, ela fornece vários tipos de resposta, incluindo um payload personalizado que pode ser usado para respostas específicas da plataforma.

Parâmetros

Os parâmetros ES têm as seguintes características:

  • Definido apenas em intents.
  • Definido por entrada, eventos, webhooks e chamadas de API do usuário final.
  • Mencionado em respostas, prompts de parâmetro, código do webhook e valores de parâmetro:
    • O formato de referência básico é $parameter-name.
    • As referências oferecem suporte à sintaxe de sufixo .original, .partial e .recent.
    • As referências podem especificar o contexto ativo: #context-name.parameter-name.
    • As referências podem especificar parâmetros de evento: #event-name.parameter-name.

Os parâmetros de CX têm as seguintes características:

  • Definido em intents e formulários de página.
  • Os parâmetros de intent e formulário são propagados para parâmetros de sessão, onde ficam disponíveis para referência durante a sessão.
  • Definido por entrada do usuário final, webhooks, predefinição de parâmetro de fulfillment e chamadas de API.
  • Mencionado em respostas, prompts de parâmetro, gerenciadores de nova solicitação, predefinições de parâmetro e código do webhook:
    • O formato de referência é $session.params.parameter-id para parâmetros de sessão e $intent.params.parameter-id para parâmetros de intent.
    • As referências de parâmetro de intent oferecem suporte à sintaxe de sufixo .original e .resolved. Os parâmetros de sessão não são compatíveis com essa sintaxe.

Entidades do sistema

O ES aceita muitas entidades do sistema.

A CX é compatível com muitas das mesmas entidades do sistema, mas há algumas diferenças. Ao migrar, verifique se as entidades do sistema que você está usando no ES também são compatíveis com CX para a mesma linguagem. Caso contrário, crie entidades personalizadas para elas.

Eventos

Os eventos ES têm as seguintes características:

  • Pode ser invocado de chamadas de API ou webhooks para corresponder a uma intent.
  • Pode definir parâmetros.
  • Um pequeno número de eventos é invocado pelas plataformas de integração.

Os eventos de CX têm as seguintes características:

  • Pode ser invocado com base em chamadas de API ou webhooks para chamar um manipulador de eventos.
  • Não é possível definir parâmetros.
  • Muitos eventos integrados podem ser usados para lidar com a falta de entradas do usuário final, entradas não reconhecidas do usuário final, parâmetros invalidados por um webhook e erros de webhook.
  • As invocações podem ser controladas pelas mesmas regras de escopo que outros gerenciadores de estado.

Intents incorporadas

O ES é compatível com as intents integradas a seguir:

Confira abaixo a descrição do suporte ao CX para intents integradas:

  • Intents de boas-vindas são compatíveis.
  • As intents substitutas não foram fornecidas. Use os eventos no-match nos manipuladores de eventos.
  • Para exemplos negativos, use a intent negativa padrão.
  • Intents de continuidade predefinidas não são fornecidas. Crie essas intents conforme exigido pelo seu agente. Por exemplo, provavelmente vai ser necessário criar uma intent para processar respostas negativas a uma pergunta do agente ("não", "não obrigado", "não, não" e assim por diante). As intents CX são reutilizáveis em todo o agente, então você só precisa defini-las uma vez. O uso de rotas de intent diferentes para essas intents comuns, em escopos diferentes, oferece um controle muito melhor sobre a conversa.

Webhooks

Os webhooks de ES têm as seguintes características:

  • É possível configurar um serviço de webhook para o agente.
  • Cada intent pode ser marcada como usando o webhook.
  • Não há suporte integrado para processar erros de webhook.
  • Ações ou nomes de intents são usados por webhooks para determinar em que parte do agente elas foram chamadas.
  • O console fornece o editor in-line.

Os webhooks do CX têm as seguintes características:

  • É possível configurar vários serviços de webhook para o agente.
  • Cada fulfillment pode especificar uma chamada de webhook.
  • Há suporte integrado para o tratamento de erros de webhook.
  • Um webhook de fulfillment do CX contém uma tag. Essa tag é semelhante a uma ação de ES, mas é usada apenas ao chamar webhooks. O serviço de webhook pode usar essas tags para determinar em que parte do agente foi chamado.
  • O console não tem um editor de código de webhook integrado. É comum usar o Cloud Functions, mas há muitas opções.

Ao migrar para o CX, você precisará alterar o código do webhook, já que as propriedades de solicitação e resposta são diferentes.

Integrações

As integrações de ES e de CX são compatíveis com diferentes plataformas. Para plataformas compatíveis com ambos os tipos de agente, pode haver diferenças de configuração.

Se a integração ES que você estava usando não for compatível com o CX, talvez seja necessário trocar de plataforma ou implementar a integração por conta própria.

Mais recursos exclusivos para CX

Há muitos outros recursos fornecidos apenas pelo CX. Considere usar esses recursos durante a migração. Exemplo:

Práticas recomendadas

Antes de migrar, conheça as práticas recomendadas de design do agente do CX. Muitas dessas práticas recomendadas de CX são semelhantes às de ES, mas algumas são exclusivas da CX.

Sobre a ferramenta de migração

A ferramenta de migração copia a maior parte dos dados ES para o agente CX e grava em um arquivo TODO com uma lista de itens que precisam ser migrados manualmente. A ferramenta só copia tipos de entidades personalizados e frases de treinamento de intent. Considere personalizar essa ferramenta para suas necessidades específicas.

Código da ferramenta de migração

Este é o código da ferramenta. Revise o código dessa ferramenta para entender o que ele faz. É possível alterar esse código para lidar com situações específicas no seu agente. Nas etapas abaixo, você executará essa ferramenta.

// Package main implements the ES to CX migration tool.
package main

import (
	"context"
	"encoding/csv"
	"flag"
	"fmt"
	"os"
	"strings"
	"time"

	v2 "cloud.google.com/go/dialogflow/apiv2"
	proto2 "cloud.google.com/go/dialogflow/apiv2/dialogflowpb"
	v3 "cloud.google.com/go/dialogflow/cx/apiv3"
	proto3 "cloud.google.com/go/dialogflow/cx/apiv3/cxpb"
	"google.golang.org/api/iterator"
	"google.golang.org/api/option"
)

// Commandline flags
var v2Project *string = flag.String("es-project-id", "", "ES project")
var v3Project *string = flag.String("cx-project-id", "", "CX project")
var v2Region *string = flag.String("es-region-id", "", "ES region")
var v3Region *string = flag.String("cx-region-id", "", "CX region")
var v3Agent *string = flag.String("cx-agent-id", "", "CX region")
var outFile *string = flag.String("out-file", "", "Output file for CSV TODO items")
var dryRun *bool = flag.Bool("dry-run", false, "Set true to skip CX agent writes")

// Map from entity type display name to fully qualified name.
var entityTypeShortToLong = map[string]string{}

// Map from ES system entity to CX system entity
var convertSystemEntity = map[string]string{
	"sys.address":         "sys.address",
	"sys.any":             "sys.any",
	"sys.cardinal":        "sys.cardinal",
	"sys.color":           "sys.color",
	"sys.currency-name":   "sys.currency-name",
	"sys.date":            "sys.date",
	"sys.date-period":     "sys.date-period",
	"sys.date-time":       "sys.date-time",
	"sys.duration":        "sys.duration",
	"sys.email":           "sys.email",
	"sys.flight-number":   "sys.flight-number",
	"sys.geo-city-gb":     "sys.geo-city",
	"sys.geo-city-us":     "sys.geo-city",
	"sys.geo-city":        "sys.geo-city",
	"sys.geo-country":     "sys.geo-country",
	"sys.geo-state":       "sys.geo-state",
	"sys.geo-state-us":    "sys.geo-state",
	"sys.geo-state-gb":    "sys.geo-state",
	"sys.given-name":      "sys.given-name",
	"sys.language":        "sys.language",
	"sys.last-name":       "sys.last-name",
	"sys.street-address":  "sys.location",
	"sys.location":        "sys.location",
	"sys.number":          "sys.number",
	"sys.number-integer":  "sys.number-integer",
	"sys.number-sequence": "sys.number-sequence",
	"sys.ordinal":         "sys.ordinal",
	"sys.percentage":      "sys.percentage",
	"sys.person":          "sys.person",
	"sys.phone-number":    "sys.phone-number",
	"sys.temperature":     "sys.temperature",
	"sys.time":            "sys.time",
	"sys.time-period":     "sys.time-period",
	"sys.unit-currency":   "sys.unit-currency",
	"sys.url":             "sys.url",
	"sys.zip-code":        "sys.zip-code",
}

// Issues found for the CSV output
var issues = [][]string{
	{"Field", "Issue"},
}

// logIssue logs an issue for the CSV output
func logIssue(field string, issue string) {
	issues = append(issues, []string{field, issue})
}

// convertEntityType converts an ES entity type to CX
func convertEntityType(et2 *proto2.EntityType) *proto3.EntityType {
	var kind3 proto3.EntityType_Kind
	switch kind2 := et2.Kind; kind2 {
	case proto2.EntityType_KIND_MAP:
		kind3 = proto3.EntityType_KIND_MAP
	case proto2.EntityType_KIND_LIST:
		kind3 = proto3.EntityType_KIND_LIST
	case proto2.EntityType_KIND_REGEXP:
		kind3 = proto3.EntityType_KIND_REGEXP
	default:
		kind3 = proto3.EntityType_KIND_UNSPECIFIED
	}
	var expansion3 proto3.EntityType_AutoExpansionMode
	switch expansion2 := et2.AutoExpansionMode; expansion2 {
	case proto2.EntityType_AUTO_EXPANSION_MODE_DEFAULT:
		expansion3 = proto3.EntityType_AUTO_EXPANSION_MODE_DEFAULT
	default:
		expansion3 = proto3.EntityType_AUTO_EXPANSION_MODE_UNSPECIFIED
	}
	et3 := &proto3.EntityType{
		DisplayName:           et2.DisplayName,
		Kind:                  kind3,
		AutoExpansionMode:     expansion3,
		EnableFuzzyExtraction: et2.EnableFuzzyExtraction,
	}
	for _, e2 := range et2.Entities {
		et3.Entities = append(et3.Entities, &proto3.EntityType_Entity{
			Value:    e2.Value,
			Synonyms: e2.Synonyms,
		})
	}
	return et3
}

// convertParameterEntityType converts a entity type found in parameters
func convertParameterEntityType(intent string, parameter string, t2 string) string {
	if len(t2) == 0 {
		return ""
	}
	t2 = t2[1:] // remove @
	if strings.HasPrefix(t2, "sys.") {
		if val, ok := convertSystemEntity[t2]; ok {
			t2 = val
		} else {
			t2 = "sys.any"
			logIssue("Intent<"+intent+">.Parameter<"+parameter+">",
				"This intent parameter uses a system entity not supported by CX English agents. See the migration guide for advice. System entity: "+t2)
		}
		return fmt.Sprintf("projects/-/locations/-/agents/-/entityTypes/%s", t2)
	}
	return entityTypeShortToLong[t2]
}

// convertIntent converts an ES intent to CX
func convertIntent(intent2 *proto2.Intent) *proto3.Intent {
	if intent2.DisplayName == "Default Fallback Intent" ||
		intent2.DisplayName == "Default Welcome Intent" {
		return nil
	}

	intent3 := &proto3.Intent{
		DisplayName: intent2.DisplayName,
	}

	// WebhookState
	if intent2.WebhookState != proto2.Intent_WEBHOOK_STATE_UNSPECIFIED {
		logIssue("Intent<"+intent2.DisplayName+">.WebhookState",
			"This intent has webhook enabled. You must configure this in your CX agent.")
	}

	// IsFallback
	if intent2.IsFallback {
		logIssue("Intent<"+intent2.DisplayName+">.IsFallback",
			"This intent is a fallback intent. CX does not support this. Use no-match events instead.")
	}

	// MlDisabled
	if intent2.MlDisabled {
		logIssue("Intent<"+intent2.DisplayName+">.MlDisabled",
			"This intent has ML disabled. CX does not support this.")
	}

	// LiveAgentHandoff
	if intent2.LiveAgentHandoff {
		logIssue("Intent<"+intent2.DisplayName+">.LiveAgentHandoff",
			"This intent uses live agent handoff. You must configure this in a fulfillment.")
	}

	// EndInteraction
	if intent2.EndInteraction {
		logIssue("Intent<"+intent2.DisplayName+">.EndInteraction",
			"This intent uses end interaction. CX does not support this.")
	}

	// InputContextNames
	if len(intent2.InputContextNames) > 0 {
		logIssue("Intent<"+intent2.DisplayName+">.InputContextNames",
			"This intent uses context. See the migration guide for alternatives.")
	}

	// Events
	if len(intent2.Events) > 0 {
		logIssue("Intent<"+intent2.DisplayName+">.Events",
			"This intent uses events. Use event handlers instead.")
	}

	// TrainingPhrases
	var trainingPhrases3 []*proto3.Intent_TrainingPhrase
	for _, tp2 := range intent2.TrainingPhrases {
		if tp2.Type == proto2.Intent_TrainingPhrase_TEMPLATE {
			logIssue("Intent<"+intent2.DisplayName+">.TrainingPhrases",
				"This intent has a training phrase that uses a template (@...) training phrase type. CX does not support this.")
		}
		var parts3 []*proto3.Intent_TrainingPhrase_Part
		for _, part2 := range tp2.Parts {
			parts3 = append(parts3, &proto3.Intent_TrainingPhrase_Part{
				Text:        part2.Text,
				ParameterId: part2.Alias,
			})
		}
		trainingPhrases3 = append(trainingPhrases3, &proto3.Intent_TrainingPhrase{
			Parts:       parts3,
			RepeatCount: 1,
		})
	}
	intent3.TrainingPhrases = trainingPhrases3

	// Action
	if len(intent2.Action) > 0 {
		logIssue("Intent<"+intent2.DisplayName+">.Action",
			"This intent sets the action field. Use a fulfillment webhook tag instead.")
	}

	// OutputContexts
	if len(intent2.OutputContexts) > 0 {
		logIssue("Intent<"+intent2.DisplayName+">.OutputContexts",
			"This intent uses context. See the migration guide for alternatives.")
	}

	// ResetContexts
	if intent2.ResetContexts {
		logIssue("Intent<"+intent2.DisplayName+">.ResetContexts",
			"This intent uses context. See the migration guide for alternatives.")
	}

	// Parameters
	var parameters3 []*proto3.Intent_Parameter
	for _, p2 := range intent2.Parameters {
		if len(p2.Value) > 0 && p2.Value != "$"+p2.DisplayName {
			logIssue("Intent<"+intent2.DisplayName+">.Parameters<"+p2.DisplayName+">.Value",
				"This field is not set to $parameter-name. This feature is not supported by CX. See: https://cloud.google.com/dialogflow/es/docs/intents-actions-parameters#valfield.")
		}
		if len(p2.DefaultValue) > 0 {
			logIssue("Intent<"+intent2.DisplayName+">.Parameters<"+p2.DisplayName+">.DefaultValue",
				"This intent parameter is using a default value. CX intent parameters do not support default values, but CX page form parameters do. This parameter should probably become a form parameter.")
		}
		if p2.Mandatory {
			logIssue("Intent<"+intent2.DisplayName+">.Parameters<"+p2.DisplayName+">.Mandatory",
				"This intent parameter is marked as mandatory. CX intent parameters do not support mandatory parameters, but CX page form parameters do. This parameter should probably become a form parameter.")
		}
		for _, prompt := range p2.Prompts {
			logIssue("Intent<"+intent2.DisplayName+">.Parameters<"+p2.DisplayName+">.Prompts",
				"This intent parameter has a prompt. Use page form parameter prompts instead. Prompt: "+prompt)
		}
		if len(p2.EntityTypeDisplayName) == 0 {
			p2.EntityTypeDisplayName = "@sys.any"
			logIssue("Intent<"+intent2.DisplayName+">.Parameters<"+p2.DisplayName+">.EntityTypeDisplayName",
				"This intent parameter does not have an entity type. CX requires an entity type for all parameters..")
		}
		parameters3 = append(parameters3, &proto3.Intent_Parameter{
			Id:         p2.DisplayName,
			EntityType: convertParameterEntityType(intent2.DisplayName, p2.DisplayName, p2.EntityTypeDisplayName),
			IsList:     p2.IsList,
		})
		//fmt.Printf("Converted parameter: %+v\n", parameters3[len(parameters3)-1])
	}
	intent3.Parameters = parameters3

	// Messages
	for _, message := range intent2.Messages {
		m, ok := message.Message.(*proto2.Intent_Message_Text_)
		if ok {
			for _, t := range m.Text.Text {
				warnings := ""
				if strings.Contains(t, "#") {
					warnings += " This message may contain a context parameter reference, but CX does not support this."
				}
				if strings.Contains(t, ".original") {
					warnings += " This message may contain a parameter reference suffix of '.original', But CX only supports this for intent parameters (not session parameters)."
				}
				if strings.Contains(t, ".recent") {
					warnings += " This message may contain a parameter reference suffix of '.recent', but CX does not support this."
				}
				if strings.Contains(t, ".partial") {
					warnings += " This message may contain a parameter reference suffix of '.partial', but CX does not support this."
				}
				logIssue("Intent<"+intent2.DisplayName+">.Messages",
					"This intent has a response message. Use fulfillment instead."+warnings+" Message: "+t)
			}
		} else {
			logIssue("Intent<"+intent2.DisplayName+">.Messages",
				"This intent has a non-text response message. See the rich response message information in the migration guide.")
		}
		if message.Platform != proto2.Intent_Message_PLATFORM_UNSPECIFIED {
			logIssue("Intent<"+intent2.DisplayName+">.Platform",
				"This intent has a message with a non-default platform. See the migration guide for advice.")
		}
	}

	return intent3
}

// migrateEntities migrates ES entities to your CX agent
func migrateEntities(ctx context.Context) error {
	var err error

	// Create ES client
	var client2 *v2.EntityTypesClient
	options2 := []option.ClientOption{}
	if len(*v2Region) > 0 {
		options2 = append(options2,
			option.WithEndpoint(*v2Region+"-dialogflow.googleapis.com:443"))
	}
	client2, err = v2.NewEntityTypesClient(ctx, options2...)
	if err != nil {
		return err
	}
	defer client2.Close()
	var parent2 string
	if len(*v2Region) == 0 {
		parent2 = fmt.Sprintf("projects/%s/agent", *v2Project)
	} else {
		parent2 = fmt.Sprintf("projects/%s/locations/%s/agent", *v2Project, *v2Region)
	}

	// Create CX client
	var client3 *v3.EntityTypesClient
	options3 := []option.ClientOption{}
	if len(*v3Region) > 0 {
		options3 = append(options3,
			option.WithEndpoint(*v3Region+"-dialogflow.googleapis.com:443"))
	}
	client3, err = v3.NewEntityTypesClient(ctx, options3...)
	if err != nil {
		return err
	}
	defer client3.Close()
	parent3 := fmt.Sprintf("projects/%s/locations/%s/agents/%s", *v3Project, *v3Region, *v3Agent)

	// Read each V2 entity type, convert, and write to V3
	request2 := &proto2.ListEntityTypesRequest{
		Parent: parent2,
	}
	it2 := client2.ListEntityTypes(ctx, request2)
	for {
		var et2 *proto2.EntityType
		et2, err = it2.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		fmt.Printf("Entity Type: %s\n", et2.DisplayName)

		if *dryRun {
			convertEntityType(et2)
			continue
		}

		request3 := &proto3.CreateEntityTypeRequest{
			Parent:     parent3,
			EntityType: convertEntityType(et2),
		}
		et3, err := client3.CreateEntityType(ctx, request3)
		entityTypeShortToLong[et3.DisplayName] = et3.Name
		if err != nil {
			return err
		}

		// ES and CX each have a quota limit of 60 design-time requests per minute
		time.Sleep(2 * time.Second)
	}
	return nil
}

// migrateIntents migrates intents to your CX agent
func migrateIntents(ctx context.Context) error {
	var err error

	// Create ES client
	var client2 *v2.IntentsClient
	options2 := []option.ClientOption{}
	if len(*v2Region) > 0 {
		options2 = append(options2,
			option.WithEndpoint(*v2Region+"-dialogflow.googleapis.com:443"))
	}
	client2, err = v2.NewIntentsClient(ctx, options2...)
	if err != nil {
		return err
	}
	defer client2.Close()
	var parent2 string
	if len(*v2Region) == 0 {
		parent2 = fmt.Sprintf("projects/%s/agent", *v2Project)
	} else {
		parent2 = fmt.Sprintf("projects/%s/locations/%s/agent", *v2Project, *v2Region)
	}

	// Create CX client
	var client3 *v3.IntentsClient
	options3 := []option.ClientOption{}
	if len(*v3Region) > 0 {
		options3 = append(options3,
			option.WithEndpoint(*v3Region+"-dialogflow.googleapis.com:443"))
	}
	client3, err = v3.NewIntentsClient(ctx, options3...)
	if err != nil {
		return err
	}
	defer client3.Close()
	parent3 := fmt.Sprintf("projects/%s/locations/%s/agents/%s", *v3Project, *v3Region, *v3Agent)

	// Read each V2 entity type, convert, and write to V3
	request2 := &proto2.ListIntentsRequest{
		Parent:     parent2,
		IntentView: proto2.IntentView_INTENT_VIEW_FULL,
	}
	it2 := client2.ListIntents(ctx, request2)
	for {
		var intent2 *proto2.Intent
		intent2, err = it2.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		fmt.Printf("Intent: %s\n", intent2.DisplayName)
		intent3 := convertIntent(intent2)
		if intent3 == nil {
			continue
		}

		if *dryRun {
			continue
		}

		request3 := &proto3.CreateIntentRequest{
			Parent: parent3,
			Intent: intent3,
		}
		_, err := client3.CreateIntent(ctx, request3)
		if err != nil {
			return err
		}

		// ES and CX each have a quota limit of 60 design-time requests per minute
		time.Sleep(2 * time.Second)
	}
	return nil
}

// checkFlags checks commandline flags
func checkFlags() error {
	flag.Parse()
	if len(*v2Project) == 0 {
		return fmt.Errorf("Need to supply es-project-id flag")
	}
	if len(*v3Project) == 0 {
		return fmt.Errorf("Need to supply cx-project-id flag")
	}
	if len(*v2Region) == 0 {
		fmt.Printf("No region supplied for ES, using default\n")
	}
	if len(*v3Region) == 0 {
		return fmt.Errorf("Need to supply cx-region-id flag")
	}
	if len(*v3Agent) == 0 {
		return fmt.Errorf("Need to supply cx-agent-id flag")
	}
	if len(*outFile) == 0 {
		return fmt.Errorf("Need to supply out-file flag")
	}
	return nil
}

// closeFile is used as a convenience for defer
func closeFile(f *os.File) {
	err := f.Close()
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR closing CSV file: %v\n", err)
		os.Exit(1)
	}
}

func main() {
	if err := checkFlags(); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR checking flags: %v\n", err)
		os.Exit(1)
	}
	ctx := context.Background()
	if err := migrateEntities(ctx); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR migrating entities: %v\n", err)
		os.Exit(1)
	}
	if err := migrateIntents(ctx); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR migrating intents: %v\n", err)
		os.Exit(1)
	}
	csvFile, err := os.Create(*outFile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR opening output file: %v", err)
		os.Exit(1)
	}
	defer closeFile(csvFile)
	csvWriter := csv.NewWriter(csvFile)
	if err := csvWriter.WriteAll(issues); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR writing CSV output file: %v", err)
		os.Exit(1)
	}
	csvWriter.Flush()
}

Migração de tipos de entidade de ferramenta

Os tipos de entidade de ES e os tipos de entidade de CX são muito semelhantes, por isso são o tipo de dados mais fácil de migrar. A ferramenta simplesmente copia os tipos de entidade como estão.

Migração de intents da ferramenta

As intents de ES e de CX são muito diferentes.

As intents de ES são usadas como elementos básicos do agente e contêm frases de treinamento, respostas, contexto para controle de conversa, configurações de webhook, eventos, ações e parâmetros de preenchimento de slot.

O Dialogflow CX moveu a maioria desses dados para outros recursos. As intents de CX têm apenas parâmetros e frases de treinamento, o que as torna reutilizáveis em todo o agente. A ferramenta só copia esses dois tipos de dados de intent para as intents CX.

Limitações da ferramenta de migração

A ferramenta de migração não é compatível com o seguinte:

  • Mega-agentes: a ferramenta não pode ler vários subagentes, mas é possível chamá-la várias vezes em cada subagente.
  • Agentes multilíngues: modifique a ferramenta para criar frases de treinamento multilíngues e entradas de entidade.
  • Verificação de entidades do sistema para idiomas diferentes do inglês: a ferramenta cria itens TODO ao encontrar entidades do sistema que não são compatíveis com o CX, supondo que o inglês é o idioma padrão e que usa uma região dos EUA. O suporte à entidade do sistema varia de acordo com o idioma e a região. Para outros idiomas e regiões, modifique a ferramenta para realizar essa verificação.

Etapas essenciais de migração

As subseções a seguir descrevem as etapas de migração a serem seguidas. Você não precisa seguir essas etapas manuais em ordem e pode até ser necessário realizá-las simultaneamente ou em uma ordem diferente. Leia as etapas e comece a planejar suas alterações antes de realmente fazer as alterações.

Depois de executar a ferramenta de migração, será possível recompilar o agente CX. Você ainda terá uma quantidade razoável de trabalho de migração a ser feito, mas a maior parte dos dados inseridos manualmente estará presente no agente CX e no arquivo TODO.

Crie seu agente do Dialogflow CX

Crie seu agente do Dialogflow CX, caso ainda não tenha feito isso. Use o mesmo idioma padrão do agente ES.

Executar a ferramenta de migração

Siga estas etapas para executar a ferramenta:

  1. Instale o Go na sua máquina, caso ainda não tenha feito isso.
  2. Crie um diretório com o nome migrate para o código da ferramenta.
  3. Copie o código da ferramenta acima em um arquivo nesse diretório chamado main.go.
  4. Modifique o código, se necessário.
  5. Crie um módulo Go nesse diretório. Exemplo:

    go mod init migrate
    
  6. Instale as bibliotecas de cliente do Dialogflow ES V2 e Dialogflow CX V3 Go:

    go get cloud.google.com/go/dialogflow/apiv2
    go get cloud.google.com/go/dialogflow/cx/apiv3
    
  7. Verifique se você configurou a autenticação da biblioteca de cliente.

  8. Execute a ferramenta e salve a saída no arquivo:

    go run main.go -es-project-id=<ES_PROJECT_ID> -cx-project-id=<CX_PROJECT_ID> \
    -cx-region-id=<CX_REGION_ID> -cx-agent-id=<CX_AGENT_ID> -out-file=out.csv
    

Solução de problemas na ferramenta de migração

Se você encontrar erros ao executar a ferramenta, verifique o seguinte:

Erro Resolução
Erro de RPC em que uma parte da frase de treinamento menciona um parâmetro não definido para a intent. Isso pode acontecer se você já tiver usado a API ES para criar parâmetros de intent de maneira inconsistente com as frases de treinamento. Para corrigir isso, renomeie o parâmetro ES no console, verifique se as frases de treinamento estão usando o parâmetro corretamente e clique em "Save". Isso também pode acontecer se as frases de treinamento fizerem referência a parâmetros inexistentes.

Depois de corrigir os erros, será necessário limpar o agente CX de intents e entidades antes de executar a ferramenta de migração novamente.

Como mover dados de intent ES para o CX

A ferramenta migra frases e parâmetros de treinamento de intents para intents CX, mas há muitos outros campos de intents ES para migrar manualmente.

Uma intent ES pode precisar de uma página CX correspondente, uma intent CX correspondente ou de ambas.

Se uma correspondência de intent ES for usada para fazer a transição de um nó de conversa específico para outro, será necessário ter duas páginas no agente relacionadas a essa intent:

  • A página original que contém a rota da intent, que faz a transição para a próxima página: a rota da intent na página original pode ter mensagens de fulfillment CX semelhantes às respostas da intent do ES. Você pode ter muitas rotas de intent nessa página. Enquanto a página original está ativa, essas rotas de intent podem fazer a transição da conversa para muitos caminhos possíveis. Muitas intents de ES compartilharão a mesma página original de CX.
  • Próxima página, que é o destino da transição para a rota da intent na página original: o fulfillment da entrada do CX da próxima página pode ter mensagens de fulfillment do CX semelhantes às respostas da intent do ES.

Se uma intent ES contiver parâmetros obrigatórios, crie uma página CX correspondente com os mesmos parâmetros em um formulário.

É comum que uma intent do CX e uma página do CX compartilhem a mesma lista de parâmetros, o que significa que uma única intent ES tem uma página CX correspondente e uma intent CX correspondente. Quando uma intent CX com parâmetros em uma rota de intent é correspondida, a conversa geralmente faz a transição para uma página com os mesmos parâmetros. Os parâmetros extraídos da correspondência da intent são propagados para os parâmetros de sessão, que estão disponíveis para preencher parcial ou totalmente os parâmetros do formulário da página.

Não existem intents substitutas e de continuidade predefinidas na CX. Consulte Intents integradas.

A tabela a seguir descreve como mapear dados de intent específicos de recursos ES para CX:

Dados de intent ES Dados de CX correspondentes Ação necessária
Frases de treinamento Frases de treinamento de intent Migrado por ferramenta. A ferramenta verifica o suporte a entidades do sistema e cria itens TODO para entidades do sistema sem suporte.
Respostas do agente Mensagens de resposta de fulfillment Veja as respostas do agente.
Contexto para controle de conversa Nenhuma Consulte Controle de caminho da conversa e estrutura.
Configuração do webhook Configuração do webhook de fulfillment Consulte webhooks.
Eventos Manipuladores de eventos de nível de fluxo ou de página Veja os eventos.
Ações Tags de webhook de fulfillment Consulte webhooks.
Parâmetros Parâmetros de intent e/ou de formulário de página Migrados para parâmetros de intent por ferramenta. Se os parâmetros forem necessários, a ferramenta criará itens TODO para, possivelmente, migrar para uma página. Consulte parâmetros.
Comandos de parâmetros Prompts de parâmetro de formulário de página Consulte preenchimento de formulários.

Criar fluxos

Crie um fluxo para cada tópico de conversação de alto nível. Os temas em cada fluxo precisam ser distintos para que a conversa não alterne com frequência entre os fluxos.

Se você estava usando um mega-agente, cada subagente precisa se tornar um ou mais fluxos.

Começar com caminhos de conversa básicos

É melhor testar o agente com o simulador enquanto itera as mudanças. Portanto, se concentre inicialmente nos caminhos básicos da conversa no início da conversa e teste conforme você faz as mudanças. Depois de fazer isso, vá para caminhos de conversa mais detalhados.

Gerenciadores de estado de nível de fluxo x nível de página

Ao criar gerenciadores de estado, considere se eles precisam ser aplicados no nível do fluxo ou da página. Um gerenciador no nível do fluxo estará no escopo sempre que o fluxo e, portanto, qualquer página dentro dele estiver ativo. Um gerenciador no nível da página só está no escopo quando a página específica está ativa. Os gerenciadores no nível do fluxo são semelhantes aos intents ES sem contexto de entrada. Os gerenciadores no nível da página são semelhantes aos intents ES com contexto de entrada.

Código do webhook

As propriedades de solicitação e resposta do webhook são diferentes para CX. Consulte a seção de webhooks.

Conectores de conhecimento

O CX ainda não oferece suporte a conectores de conhecimento. Você terá que implementá-las como intents normais ou aguardar até que o Dialogflow CX seja compatível com conectores de conhecimento.

Configurações do agente

Revise as configurações do agente de ES e ajuste as configurações do agente de CX conforme necessário.

Use o arquivo TODO

A ferramenta de migração gera um arquivo CSV. Os itens desta lista são focados em dados específicos que podem precisar de atenção. Importe esse arquivo para uma planilha. Resolva cada item na planilha, usando uma coluna para marcar a conclusão.

Migração do uso da API

Se o sistema usar a API ES para chamadas de tempo de execução ou design, será necessário atualizar esse código para usar a API CX. Se você só usa as chamadas de intent de detecção no momento da execução, essa atualização é bastante simples.

Integrações

Se o agente usa integrações, consulte a seção de integrações e faça as mudanças necessárias.

As subseções a seguir descrevem as etapas recomendadas de migração.

validação

Use a validação do agente para verificar se ele segue as práticas recomendadas.

testes

Ao executar as etapas de migração manual acima, teste seu agente com o simulador. Quando seu agente parecer estar funcionando, compare as conversas entre os agentes ES e CX e verifique se o comportamento é semelhante ou melhorado.

Ao testar essas conversas com o simulador, crie casos de teste para evitar regressões futuras.

Ambientes

Revise os ambientes de ES e atualize os ambientes de CX conforme necessário.