Bermigrasi dari Dialogflow ES ke CX

Agen Dialogflow CX memberi Anda kontrol dan alat percakapan yang lebih canggih dibandingkan agen Dialogflow ES. Jika agen Dialogflow ES Anda menangani percakapan yang kompleks, sebaiknya Anda bermigrasi ke Dialogflow CX.

Panduan ini menjelaskan cara memigrasikan agen dari Dialogflow ES ke Dialogflow CX. Kedua jenis agen ini memiliki banyak perbedaan mendasar, sehingga tidak ada cara mudah untuk melakukan migrasi ini.

Jika Anda menggunakan panduan ini untuk migrasi, harap berikan masukan positif atau negatif dengan mengklik tombol Kirim masukan di atas. Kami akan menggunakan masukan ini untuk meningkatkan kualitas panduan ini dari waktu ke waktu.

Pada tingkat tinggi, proses yang direkomendasikan adalah proses campuran otomatis/manual. Anda akan menggunakan alat yang membaca beberapa data agen Dialogflow ES, menulis data tersebut ke agen Dialogflow CX, dan mengambil daftar TODO. Kemudian, Anda akan membuat ulang agen CX lengkap menggunakan praktik terbaik, daftar TODO, dan data yang dimigrasikan oleh alat tersebut.

Memahami Dialogflow CX

Sebelum mencoba migrasi ini, Anda harus memiliki pemahaman yang kuat tentang cara kerja Dialogflow CX. Anda dapat memulainya di sini:

  1. Dasar-dasar
  2. Video pengantar
  3. Panduan memulai

Anda juga harus membaca dokumen konsep tambahan yang memiliki fitur yang mungkin akan Anda perlukan di agen baru. Fokuslah pada hal berikut:

Memahami perbedaan ES/CX

Bagian ini mencantumkan perbedaan paling penting antara Dialogflow ES dan CX. Saat melakukan langkah-langkah migrasi manual nanti, Anda harus melihat bagian ini untuk mendapatkan panduan.

Kontrol struktur dan jalur percakapan

ES menyediakan hal berikut untuk kontrol jalur percakapan dan struktur:

  • Intent digunakan sebagai elemen penyusun agen. Kapan pun dalam percakapan, intent akan dicocokkan, dan dalam arti, setiap intent adalah node untuk percakapan tersebut.
  • Context digunakan untuk mengontrol percakapan. Konteks digunakan untuk mengontrol intent mana yang dapat dicocokkan pada waktu tertentu. Konteks akan berakhir setelah sejumlah perubahan percakapan tertentu, sehingga jenis kontrol ini mungkin tidak akurat untuk percakapan panjang.

CX memberikan hierarki resource struktur dan kontrol yang lebih akurat atas jalur percakapan:

  • Halaman adalah node grafik untuk percakapan. Percakapan CX mirip dengan mesin status. Pada titik tertentu dalam percakapan, satu halaman akan aktif. Berdasarkan input atau peristiwa pengguna akhir, percakapan dapat bertransisi ke halaman lain. Sangatlah umum bagi halaman untuk tetap aktif selama beberapa kali percakapan.
  • Alur adalah kumpulan halaman yang terkait. Setiap alur harus menangani topik percakapan tingkat tinggi.
  • Pengendali status digunakan untuk mengontrol transisi dan respons. Ada tiga jenis pengendali status:
    • Rute intent: berisi intent yang harus dicocokkan, respons opsional, dan transisi halaman opsional.
    • Rute kondisi: berisi kondisi yang harus dipenuhi, respons opsional, dan transisi halaman opsional.
    • Pengendali peristiwa: berisi nama peristiwa yang harus dipanggil, respons opsional, dan transisi halaman opsional.
  • Cakupan digunakan untuk mengontrol apakah pengendali status dapat dipanggil atau tidak. Sebagian besar pengendali dikaitkan dengan halaman atau seluruh alur. Jika halaman atau alur terkait aktif, pengendali akan berada dalam cakupan, dan dapat dipanggil. Rute intent CX dalam cakupan mirip dengan intent ES dengan konteks input yang aktif.

Saat mendesain alur dan halaman agen, pastikan untuk memahami saran bagian alur dalam panduan desain agen.

Pengisian formulir

ES menggunakan pengisian slot untuk mengumpulkan parameter yang diperlukan dari pengguna akhir:

  • Parameter ini adalah parameter intent yang ditandai sebagai wajib.
  • Intent terus dicocokkan hingga semua parameter yang diperlukan dikumpulkan.
  • Anda dapat menentukan prompt yang meminta pengguna akhir untuk memberikan nilai.

CX menggunakan pengisian formulir untuk mengumpulkan parameter yang diperlukan dari pengguna akhir:

  • Parameter ini dikaitkan dengan halaman dan dikumpulkan saat halaman aktif.
  • Anda menggunakan rute kondisi untuk halaman guna menentukan bahwa pengisian formulir selesai. Kondisi ini biasanya merutekan transisi ke halaman lain.
  • Anda dapat menentukan perintah, serta pengendali perintah ulang untuk menangani beberapa upaya pengumpulan nilai dengan baik.

Transisi

ES otomatis bertransisi dari satu intent ke intent berikutnya saat input pengguna akhir cocok dengan intent. Kecocokan ini hanya dapat terjadi untuk intent yang tidak memiliki konteks input atau intent yang memiliki konteks input aktif.

Transisi CX dari satu halaman ke halaman berikutnya saat pengendali status dalam cakupan memenuhi persyaratannya dan menyediakan target transisi. Dengan menggunakan transisi ini, Anda dapat memandu pengguna akhir dalam percakapan mereka. Ada beberapa cara untuk mengontrol transisi ini:

  • Pencocokan intent dapat memicu rute intent.
  • Memenuhi kondisi dapat memicu rute kondisi.
  • Pemanggilan peristiwa dapat memicu pengendali peristiwa.
  • Pengendali permintaan ulang dapat menyebabkan transisi jika pengguna akhir gagal memberikan nilai setelah beberapa kali percobaan.
  • Anda dapat menggunakan target transisi simbolis untuk target transisi.

Respons agen

Respons agen ES dikirim ke pengguna akhir saat intent cocok:

  • Agen dapat memilih satu pesan untuk respons dari daftar kemungkinan respons.
  • Respons dapat bersifat khusus platform, yang dapat menggunakan format respons kaya.
  • Respons dapat didorong oleh webhook.

Respons agen CX dikirim ke pengguna akhir saat fulfillment dipanggil. Tidak seperti fulfillment ES, yang selalu melibatkan webhook, fulfillment CX mungkin atau mungkin tidak melibatkan pemanggilan webhook, bergantung pada apakah resource fulfillment memiliki webhook yang dikonfigurasi. Baik respons statis maupun dinamis berdasarkan respons webhook dikontrol oleh fulfillment. Ada beberapa cara untuk membuat respons agen:

  • Fulfillment dapat disediakan untuk semua jenis pengendali status.
  • Beberapa respons dapat digabungkan selama percakapan melalui antrean respons. Dalam beberapa kasus, fitur ini dapat menyederhanakan desain agen Anda.
  • CX tidak mendukung respons khusus platform bawaan. Namun, metode ini menyediakan berbagai jenis respons, termasuk payload kustom yang dapat digunakan untuk respons khusus platform.

Parameter

Parameter ES memiliki karakteristik berikut:

  • Didefinisikan hanya dalam intent.
  • Ditetapkan berdasarkan input pengguna akhir, peristiwa, webhook, dan panggilan API.
  • Dirujuk dalam respons, perintah parameter, kode webhook, dan parameter value:
    • Format referensi dasar adalah $parameter-name.
    • Referensi mendukung sintaksis akhiran .original, .partial, dan .recent.
    • Referensi dapat menentukan konteks aktif: #context-name.parameter-name.
    • Referensi dapat menentukan parameter peristiwa: #event-name.parameter-name.

Parameter CX memiliki karakteristik berikut:

  • Didefinisikan dalam intent dan bentuk halaman.
  • Parameter intent dan bentuk diterapkan ke parameter sesi, yang tersedia untuk referensi selama durasi sesi.
  • Ditetapkan berdasarkan input pengguna akhir, webhook, preset parameter fulfillment, dan panggilan API.
  • Direferensikan dalam respons, perintah parameter, pengendali permintaan ulang, preset parameter, dan kode webhook:
    • Format referensi adalah $session.params.parameter-id untuk parameter sesi, dan $intent.params.parameter-id untuk parameter intent.
    • Referensi parameter intent mendukung sintaksis akhiran .original dan .resolved. Parameter sesi tidak mendukung sintaksis ini.

Entitas sistem

ES mendukung banyak entity sistem.

CX mendukung banyak entity sistem yang sama, tetapi ada beberapa perbedaan. Saat bermigrasi, pastikan bahwa entity sistem yang Anda gunakan di ES juga didukung oleh CX untuk bahasa yang sama. Jika belum, Anda harus membuat entity kustom untuk ini.

Acara

Peristiwa ES memiliki karakteristik berikut:

  • Dapat dipanggil dari panggilan API atau webhook agar cocok dengan intent.
  • Dapat menyetel parameter.
  • Sejumlah kecil peristiwa dipanggil oleh platform integrasi.

Peristiwa CX memiliki karakteristik berikut:

  • Dapat dipanggil dari panggilan API atau webhook untuk memanggil pengendali peristiwa.
  • Tidak dapat menetapkan parameter.
  • Banyak peristiwa bawaan yang dapat digunakan untuk menangani kurangnya input pengguna akhir, input pengguna akhir yang tidak dikenali, parameter yang dibatalkan validasinya oleh webhook, dan error webhook.
  • Pemanggilan dapat dikontrol oleh aturan cakupan yang sama dengan pengendali status lainnya.

Intent bawaan

ES mendukung intent bawaan berikut:

Berikut ini penjelasan dukungan CX untuk intent bawaan:

  • Intent sambutan didukung.
  • Intent penggantian tidak disediakan. Sebagai gantinya, gunakan peristiwa no-match dalam pengendali peristiwa.
  • Untuk contoh negatif, gunakan intent negatif default.
  • Intent tindak lanjut standar tidak disediakan. Anda harus membuat intent ini sebagaimana yang diperlukan oleh agen Anda. Misalnya, Anda mungkin perlu membuat intent untuk menangani jawaban negatif atas pertanyaan agen ("tidak", "tidak, terima kasih", "tidak, saya tidak", dan seterusnya). Intent CX dapat digunakan kembali di seluruh agen, jadi Anda hanya perlu menentukannya sekali. Penggunaan rute intent yang berbeda untuk intent umum ini, dalam cakupan yang berbeda, memberi Anda kontrol percakapan yang jauh lebih baik.

Webhook

Webhook ES memiliki karakteristik berikut:

  • Anda dapat mengonfigurasi satu layanan webhook untuk agen.
  • Setiap intent dapat ditandai sebagai menggunakan webhook.
  • Tidak ada dukungan bawaan untuk menangani error webhook.
  • Tindakan intent atau nama intent digunakan oleh webhook untuk menentukan tempat asal panggilan di agen.
  • Konsol menyediakan editor inline.

Webhook CX memiliki karakteristik berikut:

  • Anda dapat mengonfigurasi beberapa layanan webhook untuk agen.
  • Setiap fulfillment dapat secara opsional menentukan panggilan webhook.
  • Ada dukungan bawaan untuk penanganan error webhook.
  • Webhook fulfillment CX berisi tag. Tag ini mirip dengan tindakan ES, tetapi hanya digunakan saat memanggil webhook. Layanan webhook dapat menggunakan tag ini untuk menentukan tempat asal panggilan di agen.
  • Konsol tidak memiliki editor kode webhook bawaan. Menggunakan Cloud Functions adalah hal yang umum, tetapi ada banyak opsi.

Saat bermigrasi ke CX, Anda harus mengubah kode webhook karena properti permintaan dan responsnya berbeda.

Integrasi

Integrasi ES dan integrasi CX mendukung berbagai platform. Untuk platform yang didukung oleh kedua jenis agen, mungkin ada perbedaan dalam konfigurasi.

Jika integrasi ES yang Anda gunakan tidak didukung oleh CX, Anda mungkin perlu beralih platform atau menerapkan integrasi sendiri.

Fitur khusus CX lainnya

Ada banyak fitur lain yang hanya disediakan oleh CX. Sebaiknya pertimbangkan untuk menggunakan fitur ini saat bermigrasi. Contoh:

Praktik terbaik

Sebelum bermigrasi, biasakan diri Anda dengan praktik terbaik desain agen CX. Banyak praktik terbaik CX ini mirip dengan praktik terbaik ES, tetapi ada juga yang unik untuk CX.

Tentang alat migrasi

Alat migrasi menyalin sebagian besar data ES ke agen CX Anda, dan menulis ke file TODO dengan daftar item yang harus dimigrasikan secara manual. Alat ini hanya menyalin jenis entity kustom dan frasa pelatihan intent. Sebaiknya pertimbangkan untuk menyesuaikan alat ini dengan kebutuhan spesifik Anda.

Kode alat migrasi

Berikut adalah kode untuk alat tersebut. Anda harus meninjau kode untuk alat ini, agar Anda memahami fungsinya. Anda mungkin ingin mengubah kode ini untuk menangani situasi tertentu di agen Anda. Dalam langkah-langkah di bawah ini, Anda akan menjalankan alat ini.

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

Migrasi alat jenis entity

Jenis entity ES dan jenis entity CX sangat mirip, sehingga merupakan jenis data yang paling mudah untuk dimigrasikan. Alat ini hanya menyalin jenis entity apa adanya.

Migrasi alat intent

Intent ES dan intent CX sangat berbeda.

Intent ES digunakan sebagai elemen penyusun agen; dan intent tersebut berisi frasa pelatihan, respons, konteks untuk kontrol percakapan, konfigurasi webhook, peristiwa, tindakan, dan parameter pengisian slot.

Dialogflow CX telah memindahkan sebagian besar data ini ke resource lain. Intent CX hanya memiliki frasa dan parameter pelatihan, sehingga intent dapat digunakan kembali di seluruh agen. Alat ini hanya menyalin kedua tipe data intent ini ke intent CX Anda.

Batasan alat migrasi

Alat migrasi tidak mendukung hal berikut:

  • Agen mega: Alat ini tidak dapat membaca dari beberapa sub-agen, tetapi Anda dapat memanggil alat ini beberapa kali terhadap setiap sub-agen.
  • Agen multibahasa: Anda harus mengubah alat untuk membuat entri entity dan frasa pelatihan multibahasa.
  • Verifikasi entitas sistem untuk bahasa non-Inggris: Alat ini membuat item TODO saat menemukan entitas sistem yang tidak didukung oleh CX, dengan asumsi bahwa bahasa Inggris adalah bahasa default, dan bahwa alat ini menggunakan wilayah AS. Dukungan entitas sistem bervariasi menurut bahasa dan wilayah. Untuk bahasa dan wilayah lain, Anda harus memodifikasi alat tersebut untuk melakukan pemeriksaan ini.

Langkah-langkah migrasi penting

Subbagian berikut menguraikan langkah-langkah migrasi yang harus dilakukan. Anda tidak perlu mengikuti langkah-langkah manual ini secara berurutan, dan bahkan mungkin perlu melakukan langkah-langkah ini secara bersamaan atau dalam urutan yang berbeda. Baca langkah-langkahnya dan mulailah merencanakan perubahan sebelum Anda benar-benar membuat perubahan.

Setelah menjalankan alat migrasi, Anda dapat membangun kembali agen CX. Anda masih akan memiliki cukup banyak pekerjaan migrasi yang harus dilakukan, tetapi sebagian besar data yang dimasukkan akan ada di agen CX Anda dan file TODO.

Membuat agen Dialogflow CX

Jika belum melakukannya, buat agen Dialogflow CX. Pastikan untuk menggunakan bahasa default yang sama dengan agen ES Anda.

Menjalankan alat migrasi

Lakukan langkah-langkah berikut untuk menjalankan alat:

  1. Jika Anda belum melakukannya, instal Go di komputer Anda.
  2. Buat direktori untuk kode alat bernama migrate.
  3. Salin kode alat di atas ke file dalam direktori ini yang bernama main.go.
  4. Ubah kode jika diperlukan untuk kasus Anda.
  5. Buat modul Go dalam direktori ini. Contoh:

    go mod init migrate
    
  6. Instal library klien Dialogflow ES V2 dan Dialogflow CX V3 Go:

    go get cloud.google.com/go/dialogflow/apiv2
    go get cloud.google.com/go/dialogflow/cx/apiv3
    
  7. Pastikan Anda telah menyiapkan autentikasi library klien.

  8. Jalankan alat, lalu simpan output ke file:

    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
    

Pemecahan masalah alat migrasi

Jika Anda mengalami error saat menjalankan alat, periksa hal-hal berikut:

Error Resolusi
Error RPC bahwa bagian frasa pelatihan menyebutkan parameter yang tidak ditetapkan untuk intent. Hal ini dapat terjadi jika sebelumnya Anda menggunakan ES API untuk membuat parameter intent dengan cara yang tidak konsisten dengan frasa pelatihan. Untuk memperbaiki masalah ini, ganti nama parameter ES dari konsol, pastikan frasa pelatihan Anda menggunakan parameter dengan benar, lalu klik simpan. Hal ini juga dapat terjadi jika frasa pelatihan Anda mereferensikan parameter yang tidak ada.

Setelah memperbaiki error, Anda perlu menghapus agen CX intent dan entity sebelum menjalankan alat migrasi lagi.

Memindahkan data intent ES ke CX

Alat ini memigrasikan frasa dan parameter pelatihan intent ke intent CX, tetapi ada banyak kolom intent ES lainnya yang perlu dimigrasikan secara manual.

Intent ES mungkin memerlukan halaman CX yang sesuai, intent CX yang sesuai, atau keduanya.

Jika pencocokan intent ES digunakan untuk mentransisikan percakapan dari node percakapan tertentu ke node percakapan lainnya, Anda akan memiliki dua halaman di agen yang terkait dengan intent ini:

  • Halaman asli yang berisi rute intent, yang akan bertransisi ke halaman berikutnya: Rute intent di halaman asli mungkin memiliki pesan fulfillment CX yang mirip dengan respons intent ES. Anda mungkin memiliki banyak rute intent di halaman ini. Saat halaman asli aktif, rute intent ini dapat mengalihkan percakapan ke banyak kemungkinan jalur. Banyak intent ES akan memiliki halaman asli CX terkait yang sama.
  • Halaman berikutnya, yang merupakan target transisi untuk rute intent di halaman asli: Fulfillment entri CX untuk halaman berikutnya mungkin memiliki pesan fulfillment CX yang mirip dengan respons intent ES.

Jika intent ES berisi parameter yang diperlukan, Anda harus membuat halaman CX yang sesuai dengan parameter yang sama dalam formulir.

Intent CX dan halaman CX biasanya memiliki daftar parameter yang sama, yang berarti bahwa satu intent ES memiliki halaman CX dan intent CX yang sesuai. Saat intent CX dengan parameter dalam rute intent cocok, percakapan sering kali bertransisi ke halaman dengan parameter yang sama. Parameter yang diekstrak dari pencocokan intent diterapkan ke parameter sesi, yang tersedia untuk parameter formulir halaman yang terisi sebagian atau penuh.

Intent penggantian dan intent tindak lanjut yang telah ditetapkan tidak ada di CX. Lihat intent bawaan.

Tabel berikut menjelaskan cara memetakan data intent tertentu dari resource ES ke CX:

Data Intent ES Data CX terkait Tindakan diperlukan
Frasa latihan Frasa pelatihan intent Dimigrasikan oleh alat. Alat pemeriksaan untuk dukungan entitas sistem, dan membuat item TODO untuk entitas sistem yang tidak didukung.
Respons agen Pesan respons fulfillment Lihat respons agen.
Konteks untuk kontrol percakapan Tidak ada Lihat Kontrol jalur percakapan dan struktur.
Setelan webhook Konfigurasi webhook fulfillment Lihat webhook.
Acara Pengendali peristiwa tingkat halaman atau tingkat alur Lihat peristiwa.
Tindakan Tag webhook fulfillment Lihat webhook.
Parameter Parameter intent dan/atau parameter Formulir halaman Dimigrasikan ke parameter intent oleh alat. Jika parameter diperlukan, alat akan membuat item TODO untuk dimigrasikan ke halaman. Lihat parameter.
Perintah parameter Dialog parameter formulir halaman Lihat pengisian formulir.

Membuat alur

Buat alur untuk setiap topik percakapan tingkat tinggi. Topik di setiap alur harus berbeda, agar percakapan tidak sering berpindah-pindah di antara alur.

Jika Anda menggunakan mega-agen, setiap sub-agen harus menjadi satu atau lebih {i>flow<i}.

Memulai dengan jalur percakapan dasar

Sebaiknya uji agen Anda dengan simulator saat melakukan iterasi perubahan. Jadi, pertama-tama Anda harus berfokus pada jalur percakapan dasar di awal percakapan, dan mengujinya saat Anda membuat perubahan. Setelah Anda melakukannya, lanjutkan ke jalur percakapan yang lebih detail.

Pengendali status tingkat alur versus tingkat halaman

Saat membuat pengendali status, pertimbangkan apakah pengendali harus diterapkan pada tingkat alur atau tingkat halaman. Pengendali tingkat alur berada dalam cakupan setiap kali alur (dan halaman apa pun dalam alur) aktif. Pengendali tingkat halaman hanya berada dalam cakupan saat halaman tertentu aktif. Pengendali tingkat alur mirip dengan intent ES tanpa konteks input. Pengendali tingkat halaman mirip dengan intent ES dengan konteks input.

Kode webhook

Properti respons dan permintaan webhook berbeda untuk CX. Lihat bagian webhook.

Konektor pengetahuan

CX belum mendukung konektor pengetahuan. Anda harus menerapkannya sebagai intent normal atau menunggu hingga Dialogflow CX mendukung konektor pengetahuan.

Setelan agen

Tinjau setelan agen ES, dan sesuaikan setelan agen CX sesuai kebutuhan.

Gunakan file TODO

Alat migrasi menghasilkan file CSV. Item dalam daftar ini difokuskan pada bagian data tertentu yang mungkin perlu diperhatikan. Impor file ini ke spreadsheet. Selesaikan setiap item dalam {i>spreadsheet<i}, menggunakan kolom untuk menandai penyelesaian.

Migrasi penggunaan API

Jika sistem Anda menggunakan ES API untuk panggilan runtime atau waktu desain, Anda harus memperbarui kode ini untuk menggunakan CX API. Jika Anda hanya menggunakan panggilan intent deteksi saat runtime, update ini akan cukup mudah.

Integrasi

Jika agen Anda menggunakan integrasi, lihat bagian integrasi, dan buat perubahan yang diperlukan.

Subbagian berikut menguraikan langkah-langkah migrasi yang direkomendasikan.

Validasi

Gunakan validasi agen untuk memeriksa apakah agen mengikuti praktik terbaik.

Pengujian

Saat melakukan langkah-langkah migrasi manual di atas, Anda harus menguji agen dengan simulator. Setelah agen tampaknya berfungsi, Anda harus membandingkan percakapan antara agen ES dan CX, dan memverifikasi bahwa perilakunya serupa atau lebih baik.

Saat menguji percakapan ini dengan simulator, Anda harus membuat kasus pengujian untuk mencegah regresi di masa mendatang.

Lingkungan

Tinjau lingkungan ES Anda dan perbarui lingkungan CX sesuai kebutuhan.