Migrer de Dialogflow ES vers CX

Les agents Dialogflow CX vous fournissent des commandes de conversation et des outils plus puissants que les agents Dialogflow ES. Si votre agent Dialogflow ES gère des conversations complexes, nous vous conseillons de migrer vers Dialogflow CX.

Ce guide explique comment migrer un agent de Dialogflow ES vers Dialogflow CX. Ces deux types d'agents présentent de nombreuses différences fondamentales. Il n'existe donc aucune méthode simple pour effectuer cette migration.

Si vous utilisez ce guide pour une migration, veuillez envoyer des commentaires positifs ou négatifs en cliquant sur le bouton Envoyer des commentaires ci-dessus. Nous utiliserons vos commentaires pour améliorer ce guide au fil du temps.

De manière générale, le processus recommandé est un processus hybride automatisé/manuel. Vous allez utiliser un outil qui lit certaines de vos données d'agent Dialogflow ES, les écrit dans votre agent Dialogflow CX et capture une liste de tâches. Vous recréez ensuite votre agent CX complet en suivant les bonnes pratiques, la liste de tâches et les données migrées par l'outil.

Comprendre Dialogflow CX

Avant d'effectuer cette migration, vous devez maîtriser parfaitement le fonctionnement de Dialogflow CX. Vous pouvez commencer ici:

  1. Général
  2. Vidéos de présentation
  3. Guides de démarrage rapide

Vous devez également consulter d'autres documents conceptuels qui comportent des fonctionnalités dont vous pourriez avoir besoin dans votre nouvel agent. Concentrez-vous sur les éléments suivants:

Comprendre les différences ES/CX

Cette section liste les principales différences entre Dialogflow ES et CX. Lorsque vous effectuerez ultérieurement les étapes de migration manuelle, reportez-vous à cette section pour obtenir des conseils.

Contrôle de la structure et du chemin de conversation

ES fournit les éléments suivants pour le contrôle de la structure et du chemin de conversation:

  • Les intents sont utilisés comme éléments de base de l'agent. À tout moment de la conversation, un intent est mis en correspondance et, en quelque sorte, chaque intent est un nœud de la conversation.
  • Le contexte permet de contrôler la conversation. Le contexte permet de contrôler les intents qui peuvent être mis en correspondance à un moment donné. Le contexte expire après un certain nombre de tours de conversation. Ce type de contrôle peut donc être inexact pour les longues conversations.

CX fournit une hiérarchie des ressources de structure et des commandes plus précises sur le chemin de conversation:

  • Les pages sont des nœuds de graphique pour la conversation. Les conversations CX sont semblables aux machines à états. À tout moment de la conversation, une page est active. En fonction des entrées ou des événements de l'utilisateur final, la conversation peut passer à une autre page. Il est courant qu'une page reste active plusieurs fois par tour.
  • Les flux sont des groupes de pages associées. Chaque flux doit gérer un sujet de conversation général.
  • Les gestionnaires d'état permettent de contrôler les transitions et les réponses. Il existe trois types de gestionnaires d'état :
    • Route d'intent: contient un intent à mettre en correspondance, des réponses facultatives et une transition de page facultative.
    • Route de condition: contient une condition à remplir, des réponses facultatives et une transition de page facultative.
    • Gestionnaire d'événements: contient un nom d'événement à appeler, des réponses facultatives et une transition de page facultative.
  • Le champ d'application permet de contrôler si un gestionnaire d'état peut être appelé. La plupart des gestionnaires sont associés à une page ou à un flux entier. Si la page ou le flux associés sont actifs, le gestionnaire entre dans le champ d'application et peut être appelé. Une route d'intent CX dans le champ d'application est semblable à un intent ES avec un contexte d'entrée actif.

Lors de la conception des flux et des pages de votre agent, veillez à bien comprendre les conseils figurant dans la section "Flux" du guide de conception de l'agent.

Remplissage de formulaire

ES utilise le remplissage d'emplacements pour collecter les paramètres requis auprès de l'utilisateur final:

  • Ces paramètres sont des paramètres d'intent marqués comme obligatoires.
  • L'intent continue d'être mis en correspondance jusqu'à ce que tous les paramètres obligatoires soient collectés.
  • Vous pouvez définir une invite demandant à l'utilisateur final de fournir une valeur.

CX utilise le remplissage de formulaire pour collecter les paramètres requis auprès de l'utilisateur final:

  • Ces paramètres sont associés à une page et sont collectés lorsque celle-ci est active.
  • Vous utilisez des routes de condition pour les pages afin de déterminer que le remplissage du formulaire est terminé. Ces routes de condition passent généralement à une autre page.
  • Vous pouvez définir une invite et des gestionnaires de nouvelles invites pour gérer correctement plusieurs tentatives de collecte d'une valeur.

Transitions

ES passe automatiquement d'un intent à l'autre lorsque l'entrée de l'utilisateur final est mise en correspondance avec un intent. Cette mise en correspondance ne peut se produire que pour les intents sans contexte d'entrée ou pour les intents avec un contexte d'entrée actif.

CX passe d'une page à l'autre lorsqu'un gestionnaire d'état dans le champ d'application répond à ses exigences et fournit une cible de transition. Ces transitions vous permettent de guider les utilisateurs finaux dans les conversations de manière fiable. Il existe plusieurs façons de contrôler ces transitions:

  • La mise en correspondance des intents peut déclencher une route d'intent.
  • Répondre à une condition peut déclencher une route de condition.
  • L'appel d'un événement peut déclencher un gestionnaire d'événements.
  • Les gestionnaires de nouvelles invites peuvent entraîner une transition lorsque l'utilisateur final ne parvient pas à fournir de valeur après plusieurs tentatives.
  • Vous pouvez utiliser des cibles de transition symboliques pour les cibles de transition.

Réponses de l'agent

Les réponses de l'agent ES sont envoyées à l'utilisateur final lorsqu'un intent est mis en correspondance:

  • L'agent peut sélectionner un message pour la réponse dans une liste de réponses possibles.
  • Les réponses peuvent être spécifiques à une plate-forme, ce qui peut utiliser des formats de réponses enrichis.
  • Les réponses peuvent être générées par des webhooks.

Les réponses de l'agent CX sont envoyées à l'utilisateur final lors de l'appel du traitement. Contrairement au fulfillment ES, qui implique toujours un webhook, le fulfillment CX peut impliquer ou non l'appel d'un webhook, selon que la ressource de fulfillment dispose ou non d'un webhook configuré. Les réponses statiques et dynamiques basées sur des réponses webhook sont contrôlées par le fulfillment. Il existe plusieurs façons de créer des réponses d'agent:

  • Le traitement peut être fourni à n'importe quel type de gestionnaire d'état.
  • Plusieurs réponses peuvent être concaténées lors d'un tour de conversation via la file d'attente de réponses. Dans certains cas, cette fonctionnalité peut simplifier la conception de votre agent.
  • CX n'est pas compatible avec les réponses intégrées spécifiques à la plate-forme. Cependant, il fournit plusieurs types de réponses, y compris une charge utile personnalisée pouvant être utilisée pour les réponses spécifiques à une plate-forme.

Paramètres

Les paramètres ES présentent les caractéristiques suivantes:

  • Définies uniquement dans les intents.
  • Défini par les entrées de l'utilisateur final, les événements, les webhooks et les appels d'API.
  • Référencer dans les réponses, les invites de paramètres, le code de webhook et les valeurs de paramètre :
    • Le format de référence de base est $parameter-name.
    • Les références acceptent la syntaxe de suffixe .original, .partial et .recent.
    • Les références peuvent spécifier le contexte actif: #context-name.parameter-name.
    • Les références peuvent spécifier des paramètres d'événement: #event-name.parameter-name.

Les paramètres CX présentent les caractéristiques suivantes:

  • Définies dans des intents et des formulaires de page.
  • Les paramètres d'intent et de formulaire sont propagés aux paramètres de session, où ils peuvent être référencées pendant toute la durée de la session.
  • Défini par les entrées de l'utilisateur final, les webhooks, le paramètre prédéfini de traitement et les appels d'API.
  • Référencement dans les réponses, les invites de paramètres, les gestionnaires de nouvelles invites, les préréglages de paramètres et le code de webhook :
    • Le format de référence est $session.params.parameter-id pour les paramètres de session et $intent.params.parameter-id pour les paramètres d'intent.
    • Les références de paramètres d'intent sont compatibles avec la syntaxe de suffixe .original et .resolved. Les paramètres de session ne sont pas compatibles avec cette syntaxe.

Entités système

ES est compatible avec de nombreuses entités système.

CX est compatible avec de nombreuses entités système identiques, mais il existe quelques différences. Lors de la migration, vérifiez que les entités système que vous utilisez en ES sont également compatibles avec CX pour la même langue. Si ce n'est pas le cas, vous devez créer des entités personnalisées pour celles-ci.

Événements

Les événements ES présentent les caractéristiques suivantes:

  • Peut être appelé à partir d'appels d'API ou de webhooks pour mettre en correspondance un intent.
  • Peut définir des paramètres.
  • Un petit nombre d'événements sont appelés par les plates-formes d'intégration.

Les événements CX présentent les caractéristiques suivantes:

  • Peut être appelé à partir d'appels d'API ou de webhooks pour appeler un gestionnaire d'événements.
  • Impossible de définir les paramètres.
  • De nombreux événements intégrés peuvent être utilisés pour gérer le manque d'entrées d'utilisateurs finaux, les entrées utilisateur non reconnues, les paramètres invalidés par un webhook et les erreurs de ce type.
  • Les appels peuvent être contrôlées par les mêmes règles de champ d'application que les autres gestionnaires d'état.

Intents intégrés

ES est compatible avec les intents intégrés suivants:

Voici comment CX est compatible avec les intents intégrés:

  • Les intents de bienvenue sont compatibles.
  • Les intents de remplacement ne sont pas fournis. Utilisez plutôt les événements no-match dans les gestionnaires d'événements.
  • Pour les exemples négatifs, utilisez l'intent négatif par défaut.
  • Les intents de suivi prédéfinis ne sont pas fournis. Vous devez créer ces intents selon les exigences de votre agent. Par exemple, vous devrez probablement créer un intent pour gérer les réponses négatives à une question d'agent ("non", "non merci", "non, je ne sais pas", etc.). Les intents CX étant réutilisables sur l'ensemble de votre agent, vous n'avez besoin de les définir qu'une seule fois. L'utilisation de différentes routes d'intent pour ces intents courants, dans des champs d'application différents, vous permet de bien mieux contrôler la conversation.

Webhooks

Les webhook ES présentent les caractéristiques suivantes:

  • Vous pouvez configurer un service de webhook pour l'agent.
  • Chaque intent peut être marqué comme utilisant le webhook.
  • Il n'est pas possible de gérer les erreurs de webhook.
  • Les webhooks utilisent les actions ou les noms d'intent pour déterminer d'où ils ont été appelés dans l'agent.
  • La console fournit l'éditeur intégré.

Les webhooks CX présentent les caractéristiques suivantes:

  • Vous pouvez configurer plusieurs services de webhook pour l'agent.
  • Chaque fulfillment peut éventuellement spécifier un appel webhook.
  • La gestion des erreurs de webhook est intégrée.
  • Un webhook de fulfillment CX contient un tag. Ce tag est semblable à une action ES, mais il n'est utilisé que lors de l'appel de webhooks. Le service de webhook peut utiliser ces tags pour déterminer à partir de quel emplacement il a été appelé dans l'agent.
  • La console ne comporte pas d'éditeur de code de webhook intégré. Il est courant d'utiliser Cloud Functions, mais il existe de nombreuses options.

Lors de la migration vers CX, vous devez modifier votre code de webhook, car les propriétés de la requête et de la réponse sont différentes.

Intégrations

Les intégrations ES et les intégrations CX sont compatibles avec différentes plates-formes. Pour les plates-formes compatibles avec les deux types d'agents, il peut y avoir des différences de configuration.

Si l'intégration ES que vous utilisiez n'est pas compatible avec CX, vous devrez peut-être changer de plate-forme ou implémenter l'intégration vous-même.

Autres fonctionnalités réservées à CX

De nombreuses autres fonctionnalités ne sont fournies que par CX. Nous vous conseillons d'utiliser ces fonctionnalités lors de la migration. Exemple :

Bonnes pratiques

Avant de procéder à la migration, familiarisez-vous avec les bonnes pratiques de conception de l'agent CX. Nombre de ces bonnes pratiques CX sont semblables aux bonnes pratiques ES, mais certaines sont propres à CX.

À propos de l'outil de migration

L'outil de migration copie la majeure partie des données ES dans votre agent CX et écrit dans un fichier TODO contenant la liste des éléments à migrer manuellement. L'outil ne copie que les types d'entités personnalisées et les phrases d'entraînement d'intent. Vous devriez envisager de personnaliser cet outil en fonction de vos besoins spécifiques.

Code de l'outil de migration

Voici le code de l'outil. Vous devez examiner le code de cet outil afin de comprendre à quoi il sert. Vous pouvez modifier ce code pour gérer des situations spécifiques dans votre agent. Vous allez exécuter cet outil dans les étapes ci-dessous.

// 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()
}

Migration des types d'entités à l'aide d'outils

Les types d'entités ES et les types d'entités CX sont très similaires et constituent le type de données le plus facile à migrer. L'outil copie simplement les types d'entités tels quels.

Migration des intents pour l'outil

Les intents ES et les intents CX sont très différents.

Les intents ES sont utilisés comme éléments de base de l'agent. Ils contiennent des phrases d'entraînement, des réponses, le contexte du contrôle de la conversation, des configurations de webhook, des événements, des actions et des paramètres de remplissage d'emplacements.

Dialogflow CX a déplacé la plupart de ces données vers d'autres ressources. Les intents CX ne comportent que des expressions et des paramètres d'entraînement, ce qui les rend réutilisables dans l'agent. L'outil ne copie ces deux types de données d'intent que dans vos intents CX.

Limites de l'outil de migration

L'outil de migration n'est pas compatible avec les éléments suivants:

  • Agents Mega: l'outil ne peut pas lire les données de plusieurs sous-agents, mais vous pouvez l'appeler plusieurs fois pour chacun d'eux.
  • Agents multilingues: vous devez modifier l'outil pour créer des phrases d'entraînement multilingues et des entrées d'entité.
  • Validation des entités système pour les langues autres que l'anglais : l'outil crée des éléments TODO lorsqu'il trouve des entités système non compatibles avec CX, en partant du principe que l'anglais est la langue par défaut et qu'il utilise une région des États-Unis. La compatibilité des entités système varie selon la langue et la région. Pour les autres langues et régions, vous devez modifier l'outil afin d'effectuer cette vérification.

Principales étapes de la migration

Les sous-sections suivantes décrivent les étapes à suivre pour la migration. Vous n'avez pas besoin de suivre ces étapes manuelles dans l'ordre. Vous devrez peut-être même effectuer ces étapes simultanément ou dans un ordre différent. Lisez les étapes et planifiez vos changements avant d'effectuer réellement les changements.

Après avoir exécuté l'outil de migration, vous pouvez recompiler votre agent CX. Il vous restera beaucoup de travail de migration, mais la majeure partie des données saisies manuellement sera présente dans votre agent CX et dans le fichier TODO.

Créer votre agent Dialogflow CX

Si vous ne l'avez pas déjà fait, créez votre agent Dialogflow CX. Assurez-vous d'utiliser la même langue par défaut que votre agent ES.

Exécuter l'outil de migration

Pour exécuter l'outil, procédez comme suit:

  1. Si vous ne l'avez pas déjà fait, installez Go sur votre ordinateur.
  2. Créez un répertoire pour le code de l'outil appelé migrate.
  3. Copiez le code de l'outil ci-dessus dans un fichier de ce répertoire nommé main.go.
  4. Si nécessaire, modifiez le code pour votre cas.
  5. Créez un module Go dans ce répertoire. Exemple :

    go mod init migrate
    
  6. Installez les bibliothèques clientes Dialogflow ES V2 et Dialogflow CX V3 Go:

    go get cloud.google.com/go/dialogflow/apiv2
    go get cloud.google.com/go/dialogflow/cx/apiv3
    
  7. Assurez-vous d'avoir configuré l'authentification de la bibliothèque cliente.

  8. Exécutez l'outil et enregistrez la sortie dans le fichier:

    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
    

Résoudre les problèmes liés à l'outil de migration

Si vous rencontrez des erreurs lors de l'exécution de l'outil, vérifiez les points suivants:

Erreur Solution
Erreur RPC indiquant qu'une partie d'expression d'entraînement mentionne un paramètre non défini pour l'intent. Cela peut se produire si vous avez déjà utilisé l'API ES pour créer des paramètres d'intent incohérents avec les phrases d'entraînement. Pour résoudre ce problème, renommez le paramètre ES depuis la console, vérifiez que vos phrases d'entraînement l'utilisent correctement, puis cliquez sur "Enregistrer". Cela peut également se produire si vos phrases d'entraînement font référence à des paramètres inexistants.

Après avoir corrigé les erreurs, vous devez effacer les intents et les entités de l'agent CX avant d'exécuter à nouveau l'outil de migration.

Déplacer des données d'intent ES vers CX

L'outil migre les phrases d'entraînement d'intent et les paramètres vers des intents CX, mais de nombreux autres champs d'intent ES doivent être migrés manuellement.

Un intent ES peut nécessiter une page CX correspondante, un intent CX correspondant, ou les deux.

Si une correspondance d'intent ES est utilisée pour faire passer la conversation d'un nœud de conversation particulier à un autre, votre agent doit contenir deux pages liées à cet intent:

  • Page d'origine contenant la route d'intent, qui va passer à la page suivante : la route d'intent de la page d'origine peut contenir des messages de traitement CX semblables aux réponses d'intent ES. Cette page peut comporter de nombreuses routes d'intent. Lorsque la page d'origine est active, ces routes d'intent peuvent transférer la conversation vers de nombreux chemins possibles. De nombreux intents ES partagent la même page d'origine CX.
  • Page suivante, qui est la cible de transition de la route d'intent sur la page d'origine : le traitement de l'entrée CX pour la page suivante peut contenir des messages de traitement CX semblables aux réponses d'intent ES.

Si un intent ES contient les paramètres requis, vous devez créer une page CX correspondante, avec les mêmes paramètres dans un formulaire.

Il est courant qu'un intent CX et qu'une page CX partagent la même liste de paramètres, ce qui signifie qu'un même intent ES possède une page CX et un intent CX correspondants. Lorsqu'un intent CX avec des paramètres dans une route d'intent est mis en correspondance, la conversation passe souvent à une page avec les mêmes paramètres. Les paramètres extraits de la correspondance d'intent sont propagés aux paramètres de session, qui sont disponibles pour remplir partiellement ou entièrement les paramètres de formulaire de page.

Les intents de remplacement et les intents de suivi prédéfinis n'existent pas dans CX. Consultez la section Intents intégrés.

Le tableau suivant explique comment mapper des données d'intent spécifiques à partir des ressources ES et CX:

Données d'intent ES Données CX correspondantes Action requise
Phrases d'entraînement Phrases d'entraînement d'intent Migré par un outil. L'outil vérifie la compatibilité des entités système et crée des éléments "TODO" pour les entités système non compatibles.
Réponses de l'agent Messages de réponse de traitement Consultez les réponses de l'agent.
Contexte pour le contrôle de la conversation Aucun Consultez la page Contrôle de la structure et du chemin de conversation.
Paramètre du webhook Configuration du webhook de traitement Voir webhooks.
Événements Gestionnaires d'événements au niveau du flux ou de la page Voir Événements.
Actions Balises webhook de traitement Voir webhooks.
Paramètres Paramètres d'intent et/ou de formulaire de page Migration vers les paramètres d'intent par outil. Si les paramètres sont requis, l'outil crée des éléments "TODO" pour éventuellement migrer vers une page. Voir Paramètres.
Invites de paramètres Invites de paramètres de formulaire de page Pour en savoir plus, consultez Remplissage d'un formulaire.

Créer des flux

Créez un flux pour chaque sujet de conversation général. Les sujets de chaque flux doivent être distincts, afin que la conversation ne passe pas fréquemment d'un flux à l'autre.

Si vous utilisiez un méga-agent, chaque sous-agent doit devenir un ou plusieurs flux.

Commencer par des chemins de conversation de base

Il est préférable de tester votre agent avec le simulateur lors de l'itération des modifications. Vous devez donc commencer par vous concentrer sur les chemins de conversation de base au début de la conversation et effectuer des tests au fur et à mesure que vous apportez des modifications. Une fois que vous les aurez utilisés, passez à des chemins de conversation plus détaillés.

Gestionnaires d'état au niveau du flux ou au niveau de la page

Lorsque vous créez des gestionnaires d'état, déterminez s'ils doivent être appliqués au niveau du flux ou de la page. Un gestionnaire au niveau du flux entre dans le champ d'application chaque fois que le flux (et donc toute page qu'il contient) est actif. Un gestionnaire au niveau de la page n'entre dans le champ d'application que lorsque la page en question est active. Les gestionnaires au niveau du flux sont semblables aux intents ES sans contexte d'entrée. Les gestionnaires au niveau de la page sont semblables aux intents ES avec un contexte d'entrée.

Code du webhook

Les propriétés des requêtes et des réponses webhooks sont différentes pour CX. Consultez la section Webhooks.

Connecteurs de connaissances

CX n'est pas encore compatible avec les connecteurs de connaissances. Vous devrez les implémenter comme des intents normaux ou attendre que Dialogflow CX soit compatible avec les connecteurs de connaissances.

Paramètres de l'agent

Vérifiez les paramètres de votre agent ES et ajustez ceux de votre agent CX si nécessaire.

Utiliser le fichier TODO

L'outil de migration génère un fichier CSV. Les éléments de cette liste sont axés sur des données spécifiques qui peuvent nécessiter votre attention. Importez ce fichier dans une feuille de calcul. Résolvez chaque élément de la feuille de calcul, en utilisant une colonne pour marquer comme terminé.

Migration vers l'utilisation des API

Si votre système utilise l'API ES pour les appels d'exécution ou de conception, vous devez mettre à jour ce code pour utiliser l'API CX. Si vous n'utilisez les appels de détection d'intent qu'au moment de l'exécution, cette mise à jour devrait être assez simple.

Intégrations

Si votre agent utilise des intégrations, consultez la section sur les intégrations et apportez les modifications nécessaires.

Les sous-sections suivantes décrivent les étapes de migration recommandées.

Validation

Utilisez la validation de l'agent pour vérifier que votre agent respecte les bonnes pratiques.

Tests

En effectuant les étapes de migration manuelle ci-dessus, vous devez tester votre agent avec le simulateur. Une fois que votre agent semble fonctionner, vous devez comparer les conversations entre vos agents ES et CX, et vérifier que leur comportement est similaire ou amélioré.

Lorsque vous testez ces conversations avec le simulateur, vous devez créer des scénarios de test pour éviter les régressions futures.

Environnements

Examinez vos environnements ES et mettez à jour vos environnements CX si nécessaire.