העברה מ-Dialogflow ES ל-Dialogflow CX

סוכני Dialogflow CX מספקים כלים ואמצעי בקרה יעילים יותר לשיחות מאשר סוכני Dialogflow ES. אם הנציג שלכם ב-Dialogflow ES מטפל בשיחות מורכבות, כדאי לכם לשקול לעבור ל-Dialogflow CX.

במדריך הזה מוסבר איך להעביר נציג מ-Dialogflow ES ל-Dialogflow CX. יש הרבה הבדלים מהותיים בין שני סוגי הנציגים האלה, ולכן אין דרך פשוטה לבצע את ההעברה הזו.

אם אתם משתמשים במדריך הזה להעברה, נשמח לקבל מכם משוב חיובי או שלילי בלחיצה על הלחצן שליחת משוב שלמעלה. נשתמש במשוב הזה כדי לשפר את המדריך הזה לאורך זמן.

ברמה הכללית, התהליך המומלץ הוא תהליך היברידי אוטומטי/ידני. תשתמשו בכלי שקורא חלק מהנתונים של הסוכן ב-Dialogflow ES, כותב את הנתונים האלה לסוכן CX ב-Dialogflow CX ויוצר רשימת משימות. לאחר מכן, יוצרים מחדש את הסוכן המלא של Dialogflow CX באמצעות השיטות המומלצות, רשימת המשימות והנתונים שהועברו על ידי הכלי.

הסבר על Dialogflow CX

לפני שתנסו לבצע את ההעברה הזו, חשוב שתבינו היטב איך פועלים תהליכים ב-Dialogflow CX. אפשר להתחיל כאן:

כדאי גם לקרוא מסמכי קונספט נוספים שכוללים תכונות שכנראה תצטרכו בנציג החדש. כדאי להתמקד בתחומים הבאים:

ההבדלים בין Dialogflow ES ל-Dialogflow CX

בקטע הזה מפורטים ההבדלים החשובים ביותר בין Dialogflow ES לבין Dialogflow CX. כשמבצעים את שלבי ההעברה הידנית בהמשך, כדאי לעיין בקטע הזה כדי לקבל הנחיות.

שליטה במבנה ובנתיב השיחה

ב-Dialogflow ES יש את האפשרויות הבאות לשליטה במבנה ובנתיב השיחה:

  • כוונות משמשות כאבני הבניין של הנציג. בכל שלב בשיחה, מתבצעת התאמה לכוונה, ובמובן מסוים, כל כוונה היא צומת בשיחה.
  • ההקשר משמש לשליטה בשיחה. ההקשר משמש כדי לקבוע אילו כוונות יכולות להתאים בכל זמן נתון. ההקשר פג אחרי מספר מסוים של חילופי דברים בשיחה, ולכן סוג כזה של בקרה יכול להיות לא מדויק בשיחות ארוכות.

ב-Dialogflow CX יש היררכיה של משאבים מבניים ואמצעי בקרה מדויקים יותר על נתיב השיחה:

  • דפים הם צומתי תרשים של השיחה. השיחות ב-Dialogflow CX דומות למכונות מצבים. בכל שלב בשיחה, דף אחד פעיל. בהתאם לקלט או לאירועים של משתמש הקצה, השיחה יכולה לעבור לדף אחר. בדרך כלל דף נשאר פעיל במשך כמה סבבי שיחה.
  • תהליכים הם קבוצות של דפים קשורים. כל תהליך צריך לטפל בנושא שיחה ברמה גבוהה.
  • מטפלים במצבים משמשים לשליטה במעברים ובתגובות. יש שלושה סוגים של פונקציות לטיפול במצבים:
    • Intent route: מכיל כוונה שצריכה להתאים, תשובות אופציונליות ומעבר אופציונלי בין דפים.
    • נתיב תנאי: מכיל תנאי שצריך לעמוד בו, תשובות אופציונליות ומעבר אופציונלי בין דפים.
    • גורם מטפל באירועים: מכיל שם אירוע שצריך להפעיל, תגובות אופציונליות ומעבר אופציונלי בין דפים.
  • היקף משמש כדי לקבוע אם אפשר לקרוא ל-state handler. רוב ה-handlers משויכים לדף או לתהליך שלם. אם הדף או התהליך המשויכים פעילים, ה-handler נמצא בהיקף וניתן לקרוא לו. נתיב Intent של Dialogflow CX שנמצא בהיקף דומה ל-Intent של Dialogflow ES עם הקשר קלט פעיל.

כשאתם מתכננים את התהליכים והדפים של הנציג, חשוב שתבינו את העצות שמופיעות בקטע "תהליכים" במדריך לתכנון נציגים.

מילוי טפסים

ב-Dialogflow ES משתמשים במילוי משבצות כדי לאסוף את הפרמטרים הנדרשים ממשתמש הקצה:

  • הפרמטרים האלה הם פרמטרים של Intent שמסומנים כנדרשים.
  • ההתאמה ל-Intent נמשכת עד שכל הפרמטרים הנדרשים נאספים.
  • אתם יכולים להגדיר הנחיה שמבקשת ממשתמש הקצה לספק ערך.

ב-Dialogflow CX משתמשים במילוי טפסים כדי לאסוף את הפרמטרים הנדרשים ממשתמש הקצה:

  • הפרמטרים האלה משויכים לדף ונאספים כשהדף פעיל.
  • אתם משתמשים בנתיבים מותנים לדפים כדי לקבוע שמילוי הטופס הושלם. התנאים האלה בדרך כלל מעבירים לדף אחר.
  • אתם יכולים להגדיר הנחיה וגם רכיבי handler להנחיות חוזרות כדי לטפל בצורה חלקה בכמה ניסיונות לאסוף ערך.

מעברים

ב-Dialogflow ES, המעבר מכוונה אחת לאחרת מתבצע באופן אוטומטי כשקלט של משתמש הקצה תואם לכוונה. ההתאמה הזו יכולה להתרחש רק בכוונות שאין להן הקשר קלט, או בכוונות שיש להן הקשר קלט פעיל.

ב-Dialogflow CX, המעבר מדף אחד לדף הבא מתבצע כש-state handler בהיקף עומד בדרישות שלו ומספק יעד מעבר. בעזרת המעברים האלה, אתם יכולים להנחות את משתמשי הקצה בשיחות בצורה מהימנה. יש כמה דרכים לשלוט במעברים האלה:

  • התאמת כוונה יכולה להפעיל מסלול כוונה.
  • עמידה בתנאי יכולה להפעיל מסלול תנאי.
  • הפעלה של אירוע יכולה להפעיל גורם מטפל באירועים.
  • רכיבי handler של בקשות חוזרות יכולים לגרום למעבר כשמשתמש הקצה לא מצליח לספק ערך אחרי כמה ניסיונות.
  • אפשר להשתמש ביעדי מעבר סמליים כיעדי מעבר.

תשובות של סוכנים

התשובות של הסוכן ב-Dialogflow ES נשלחות למשתמש הקצה כשיש התאמה ל-intent:

  • הנציג יכול לבחור הודעה אחת לתשובה מתוך רשימה של תשובות אפשריות.
  • התשובות יכולות להיות ספציפיות לפלטפורמה, ולהשתמש בפורמטים של תשובות עשירות.
  • התשובות יכולות להתבסס על תגובות לפעולות לפיתוח האתר (webhooks).

התשובות של סוכן Dialogflow CX נשלחות למשתמש הקצה כשמתבצעת קריאה למילוי הבקשה. בניגוד למילוי בקשות ב-Dialogflow ES, שתמיד כולל webhook, מילוי בקשות ב-Dialogflow CX יכול לכלול קריאה ל-webhook או לא, בהתאם להגדרות של משאב מילוי הבקשות. התשובות הסטטיות והדינמיות שמבוססות על תגובות של webhook נשלטות על ידי מילוי הבקשה. יש כמה דרכים ליצור תשובות של נציגים:

  • אפשר לספק מילוי בקשה לכל סוג של handler של מצב.
  • אפשר לשרשר כמה תשובות במהלך תור בשיחה באמצעות תור התשובות. במקרים מסוימים, התכונה הזו יכולה לפשט את עיצוב הנציג.
  • ‫Dialogflow CX לא תומך בתשובות מובנות ספציפיות לפלטפורמה. עם זאת, הוא מספק סוגים רבים של תגובות, כולל מטען ייעודי (payload) מותאם אישית שאפשר להשתמש בו לתגובות ספציפיות לפלטפורמה.

פרמטרים

לפרמטרים של Dialogflow ES יש את המאפיינים הבאים:

  • מוגדר רק ב-Intent.
  • מוגדר על ידי קלט של משתמש הקצה, אירועים, רכיבי webhook וקריאות ל-API.
  • הפניות בתשובות, בהנחיות לפרמטרים, בקוד של ה-webhook ובערכי הפרמטרים:
    • הפורמט הבסיסי של קובץ העזר הוא $parameter-name.
    • ההפניות תומכות בתחביר הסיומות .original,‏ .partial ו-.recent.
    • ההפניות יכולות לציין את ההקשר הפעיל: #context-name.parameter-name.
    • הפניות יכולות לציין פרמטרים של אירועים: #event-name.parameter-name.

לפרמטרים של Dialogflow CX יש את המאפיינים הבאים:

  • מוגדר בכוונות ובטפסים של דפים.
  • הפרמטרים של הכוונה והטופס מופצים לפרמטרים של הסשן, שבהם הם זמינים להפניה למשך הסשן.
  • מוגדר על ידי קלט של משתמש קצה, רשתות לפעולות מאפליקציות, הגדרה מראש של פרמטר מילוי בקשה וקריאות ל-API.
  • הפניות בתשובות, הנחיות לפרמטרים, פונקציות טיפול בהנחיות חוזרות, הגדרות קבועות מראש של פרמטרים וקוד webhook:
    • פורמט ההפניה הוא $session.params.parameter-id לפרמטרים של סשן ו-$intent.params.parameter-id לפרמטרים של Intent.
    • הפניות לפרמטרים של Intent תומכות בתחביר של הסיומות .original ו-.resolved. התחביר הזה לא נתמך בפרמטרים של סשנים.

ישויות מערכת

ב-Dialogflow ES יש תמיכה בהרבה ישויות מערכת.

ב-Dialogflow CX יש תמיכה בהרבה ישויות מערכת דומות, אבל יש כמה הבדלים. כשעוברים בין הגרסאות, חשוב לוודא שהישויות של המערכת שבהן אתם משתמשים ב-Dialogflow ES נתמכות גם ב-Dialogflow CX באותה שפה. אם לא, צריך ליצור ישויות בהתאמה אישית בשבילם.

אירועים

לאירועים ב-Dialogflow ES יש את המאפיינים הבאים:

  • אפשר להפעיל אותם מקריאות ל-API או מ-webhooks כדי להתאים לכוונה.
  • אפשר להגדיר פרמטרים.
  • מספר קטן של אירועים מופעל על ידי פלטפורמות שילוב.

לאירועים ב-Dialogflow CX יש את המאפיינים הבאים:

  • אפשר להפעיל אותו מקריאות ל-API או מ-webhooks כדי לקרוא לגורם מטפל באירועים.
  • אי אפשר להגדיר פרמטרים.
  • אפשר להשתמש בהרבה אירועים מובנים כדי לטפל במקרים שבהם משתמש הקצה לא הזין קלט, הזין קלט לא מזוהה, פרמטרים בוטלו על ידי webhook ושגיאות webhook.
  • אפשר לשלוט בהפעלות באמצעות אותם כללי היקף כמו ב-state handlers אחרים.

כוונות מובנות

ב-Dialogflow ES יש תמיכה בכוונות המובנות הבאות:

בטבלה הבאה מפורטות הכוונות המובנות שנתמכות ב-Dialogflow CX:

  • יש תמיכה באובייקטים מסוג Intent לפתיחת שיחה.
  • אין כוונות חלופיות. במקום זאת, השתמשו באירועים מסוג no-match בגורמים מטפלים באירועים.
  • כדי להוסיף דוגמאות שליליות, משתמשים בכוונת ברירת המחדל השלילית.
  • לא מסופקות כוונות מוגדרות מראש לצורכי מעקב. אתם צריכים ליצור את ה-intent האלה לפי הדרישות של הסוכן. לדוגמה, סביר להניח שתצטרכו ליצור Intent כדי לטפל בתשובות שליליות לשאלה של הנציג ("לא", "לא תודה", "לא, אני לא" וכו'). אפשר להשתמש בכוונות של Dialogflow CX שוב ושוב בנציג, אז צריך להגדיר אותן רק פעם אחת. שימוש בנתיבי כוונה שונים לכוונות הנפוצות האלה, בהיקפים שונים, נותן לכם שליטה טובה יותר על השיחה.

תגובות לפעולה מאתר אחר (webhook)

ל-webhooks של Dialogflow ES יש את המאפיינים הבאים:

  • אפשר להגדיר שירות webhook אחד לסוכן.
  • אפשר לסמן כל כוונה ככזו שמשתמשת ב-webhook.
  • אין תמיכה מובנית בטיפול בשגיאות של תגובות לפעולה מאתר אחר (webhook).
  • פעולות Intent או שמות Intent משמשים לפעולות לפיתוח API כדי לקבוע מאיפה בנציג הן נקראו.
  • במסוף יש עריכה ישירה.

ל-Dialogflow CX webhooks יש את המאפיינים הבאים:

  • אפשר להגדיר כמה שירותי webhook לסוכן.
  • כל מילוי בקשה יכול לציין קריאה ל-webhook.
  • יש תמיכה מובנית בטיפול בשגיאות של webhook.
  • ‫webhook למילוי בקשות ב-Dialogflow CX מכיל תג. התג הזה דומה לפעולה ב-Dialogflow ES, אבל משתמשים בו רק כשקוראים ל-webhooks. שירות ה-webhook יכול להשתמש בתגים האלה כדי לקבוע מאיפה בנציג הוא נקרא.
  • במסוף אין עורך קוד מובנה של תגובה לפעולה מאתר אחר. בדרך כלל משתמשים בפונקציות Cloud Run, אבל יש הרבה אפשרויות.

כשעוברים ל-Dialogflow CX, צריך לשנות את קוד ה-webhook כי מאפייני הבקשה והתגובה שונים.

שילובים

השילובים של Dialogflow ES והשילובים של Dialogflow CX תומכים בפלטפורמות שונות. בפלטפורמות שנתמכות על ידי שני סוגי הסוכנים, יכולים להיות הבדלים בהגדרות.

אם השילוב של Dialogflow ES שהשתמשתם בו לא נתמך ב-Dialogflow CX, יכול להיות שתצטרכו להחליף פלטפורמה או להטמיע את השילוב בעצמכם.

תכונות נוספות שקיימות רק ב-Dialogflow CX

יש הרבה תכונות אחרות שזמינות רק ב-Dialogflow CX. כדאי להשתמש בתכונות האלה במהלך ההעברה. לדוגמה:

שיטות מומלצות

לפני המעבר, כדאי להכיר את השיטות המומלצות לעיצוב סוכנים ב-Dialogflow CX. רבות מהשיטות המומלצות האלה ל-Dialogflow CX דומות לשיטות המומלצות ל-Dialogflow ES, אבל חלקן ייחודיות ל-Dialogflow CX.

מידע על כלי ההעברה

כלי ההעברה מעתיק את רוב הנתונים של Dialogflow ES לסוכן Dialogflow CX, וכותב קובץ TODO עם רשימת פריטים שצריך להעביר באופן ידני. הכלי מעתיק רק סוגי ישויות בהתאמה אישית וביטויי אימון של Intent. כדאי להתאים אישית את הכלי הזה לצרכים הספציפיים שלכם.

קוד כלי ההעברה

הנה הקוד של הכלי. כדאי לבדוק את הקוד של הכלי הזה כדי להבין מה הוא עושה. יכול להיות שתרצו לשנות את הקוד הזה כדי לטפל במצבים ספציפיים בנציג שלכם. בשלבים הבאים תפעילו את הכלי הזה.

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

העברת סוגי ישויות באמצעות כלי

סוגי הישויות ב-Dialogflow ES וסוגי הישויות ב-Dialogflow CX דומים מאוד, ולכן הכי קל להעביר את סוג הנתונים הזה. הכלי פשוט מעתיק את סוגי הישויות כמו שהם.

העברת כוונות של כלים

כוונות ב-Dialogflow ES וכוונות ב-Dialogflow CX שונות מאוד.

הכוונות של Dialogflow ES משמשות כאבני הבניין של הנציג, והן מכילות ביטויי אימון, תשובות, הקשר לבקרת השיחה, הגדרות של webhook, אירועים, פעולות ופרמטרים למילוי משבצות.

ב-Dialogflow CX, רוב הנתונים האלה הועברו למשאבים אחרים. לכוונות ב-Dialogflow CX יש רק ביטויי אימון ופרמטרים, ולכן אפשר להשתמש בהן שוב ושוב בכל הסוכן. הכלי מעתיק רק את שני סוגי הנתונים האלה של כוונות המשתמשים לכוונות המשתמשים ב-Dialogflow CX.

מגבלות של כלי ההעברה

כלי ההעברה לא תומך באפשרויות הבאות:

  • מגה סוכנים: הכלי לא יכול לקרוא מכמה סוכני משנה, אבל אפשר להפעיל את הכלי כמה פעמים על כל סוכן משנה.
  • נציגים רב-לשוניים: צריך לשנות את הכלי כדי ליצור ביטויי אימון ורשומות של ישויות בכמה שפות.
  • אימות ישויות מערכת בשפות שאינן אנגלית: הכלי יוצר פריטים לביצוע כשהוא מוצא ישויות מערכת שלא נתמכות על ידי Dialogflow CX, בהנחה שאנגלית היא שפת ברירת המחדל ושהוא משתמש באזור של ארה"ב. התמיכה בישויות מערכת משתנה בהתאם לשפה ולאזור. בשפות ובאזורים אחרים, צריך לשנות את הכלי כדי לבצע את הבדיקה הזו.

שלבי ההעברה החיוניים

בסעיפי המשנה הבאים מפורטים שלבי ההעברה שצריך לבצע. לא צריך לבצע את השלבים הידניים האלה לפי הסדר, ואולי אפילו תצטרכו לבצע אותם בו-זמנית או בסדר אחר. קראו את השלבים והתחילו לתכנן את השינויים לפני שתבצעו אותם בפועל.

אחרי שמריצים את כלי ההעברה, אפשר לבנות מחדש את סוכן ה-CX ב-Dialogflow CX. עדיין תצטרכו לבצע לא מעט עבודת העברה, אבל רוב הנתונים שהוזנו ידנית יהיו זמינים בסוכן Dialogflow CX ובקובץ TODO.

יצירת סוכן Dialogflow CX

אם עדיין לא עשיתם זאת, צרו סוכן Dialogflow CX. חשוב להשתמש באותה שפת ברירת מחדל כמו של הנציג ב-Dialogflow ES.

הפעלת כלי ההעברה

כדי להפעיל את הכלי:

  1. אם עדיין לא עשיתם זאת, התקינו את Go במחשב.
  2. יוצרים ספרייה לקוד הכלי בשם migrate.
  3. מעתיקים את קוד הכלי שלמעלה לקובץ בספרייה הזו שנקרא main.go.
  4. אם צריך, משנים את הקוד בהתאם למקרה הספציפי.
  5. יוצרים מודול Go בספרייה הזו. לדוגמה:

    go mod init migrate
    
  6. מתקינים את ספריות הלקוח של Dialogflow ES V2 ו-Dialogflow CX V3 Go:

    go get cloud.google.com/go/dialogflow/apiv2
    go get cloud.google.com/go/dialogflow/cx/apiv3
    
  7. מוודאים שהגדרתם אימות של ספריית לקוח.

  8. מפעילים את הכלי ושומרים את הפלט בקובץ:

    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
    

פתרון בעיות בכלי ההעברה

אם נתקלתם בשגיאות בזמן הפעלת הכלי, בדקו את הדברים הבאים:

שגיאה רזולוציה
שגיאת RPC שבה חלק מביטוי לאימון מציין פרמטר שלא מוגדר ל-Intent. זה יכול לקרות אם השתמשתם בעבר ב-Dialogflow ES API כדי ליצור פרמטרים של כוונות באופן שלא תאם לביטויי האימון. כדי לפתור את הבעיה, משנים את השם של הפרמטר של Dialogflow ES מהמסוף, מוודאים שהפרמטר מוגדר בצורה נכונה בביטויי האימון ולוחצים על "שמירה". זה יכול לקרות גם אם ביטויי האימון מפנים לפרמטרים שלא קיימים.

אחרי שתתקנו את השגיאות, תצטרכו למחוק את הכוונות והישויות של סוכן ה-CX ב-Dialogflow CX לפני שתפעילו שוב את כלי ההעברה.

העברת נתוני כוונה מ-Dialogflow ES ל-Dialogflow CX

הכלי מעביר את הפרמטרים וביטויי ה-Intent לאימון ל-Dialogflow CX, אבל יש הרבה שדות אחרים של Intent ב-Dialogflow ES שצריך להעביר באופן ידני.

יכול להיות ש-intent ב-Dialogflow ES ידרוש דף תואם ב-Dialogflow CX, ‏intent תואם ב-Dialogflow CX או את שניהם.

אם משתמשים בהתאמה ל-intent ב-Dialogflow ES כדי להעביר את השיחה מצומת מסוים לצומת אחר, צריכים להיות בסוכן שני דפים שקשורים ל-intent הזה:

  • הדף המקורי שמכיל את נתיב ה-intent, שיעבור לדף הבא: לנתיב ה-intent בדף המקורי יכולות להיות הודעות מילוי בקשות ב-Dialogflow CX שדומות לתגובות ה-intent ב-Dialogflow ES. יכול להיות שיש בדף הזה הרבה נתיבי Intent. כשהדף המקורי פעיל, נתיבי הכוונה האלה יכולים להעביר את השיחה להרבה נתיבים אפשריים. הרבה כוונות ב-Dialogflow ES ישתפו את אותו דף מקורי תואם ב-Dialogflow CX.
  • הדף הבא, שהוא יעד המעבר של נתיב ה-intent בדף המקורי: מילוי הבקשה בכניסה ל-Dialogflow CX בדף הבא יכול לכלול הודעות מילוי בקשה ב-Dialogflow CX שדומות לתגובות ל-intent ב-Dialogflow ES.

אם כוונה ב-Dialogflow ES מכילה פרמטרים נדרשים, צריך ליצור דף תואם ב-Dialogflow CX עם אותם פרמטרים בטופס.

בדרך כלל, ל-intent ולדף ב-Dialogflow CX יש את אותה רשימת פרמטרים. כלומר, ל-intent אחד ב-Dialogflow ES יש דף תואם ו-intent תואם ב-Dialogflow CX. כשמתבצעת התאמה ל-intent של Dialogflow CX עם פרמטרים בנתיב intent, השיחה בדרך כלל עוברת לדף עם אותם פרמטרים. הפרמטרים שחולצו מההתאמה ל-Intent מועברים לפרמטרים של הסשן, שזמינים למילוי חלקי או מלא של פרמטרים של טופס בדף.

ב-Dialogflow CX אין כוונות חלופיות וכוונות מוגדרות מראש לצורכי מעקב. מידע נוסף על כוונות מובנות

בטבלה הבאה מוסבר איך למפות נתונים ספציפיים של כוונות מ-Dialogflow ES למשאבים של Dialogflow CX:

נתוני Intent ב-Dialogflow ES נתונים תואמים ב-Dialogflow CX נדרשת פעולה
ביטויי אימון ביטויי אימון של כוונה הועבר באמצעות כלי. הכלי בודק אם יש תמיכה בישויות מערכת, ויוצר פריטים לביצוע עבור ישויות מערכת שלא נתמכות.
תשובות של סוכנים הודעות תגובה למילוי בקשות תשובות של נציגים
הקשר לשליטה בשיחה ללא מידע נוסף על מבנה השיחה והשליטה בנתיב השיחה
הגדרת תגובה לפעולה מאתר אחר (webhook) הגדרת תגובה לפעולה מאתר אחר (webhook) למילוי בקשות מידע נוסף על תגובות לפעילות באתר (webhooks)
אירועים גורמים מטפלים באירועים ברמת התהליך או ברמת הדף אירועים
פעולות תגי webhook למילוי בקשות מידע נוסף על תגובות לפעילות באתר (webhooks)
פרמטרים פרמטרים של כוונה ו/או פרמטרים של טופס בדף הועברו לפרמטרים של Intent לפי כלי. אם הפרמטרים נדרשים, הכלי יוצר פריטים לביצוע כדי להעביר אותם לדף. לפרמטרים
הנחיות לפרמטרים הנחיות לפרמטרים של טופס בדף מידע נוסף על מילוי טפסים

יצירת רצפי פעולות

צרו תהליך לכל נושא שיחה ברמה גבוהה. הנושאים בכל תהליך צריכים להיות נפרדים, כדי שהשיחה לא תעבור לעיתים קרובות בין תהליכים.

אם השתמשתם במגה סוכן, כל סוכן משנה צריך להפוך לתהליך אחד או יותר.

מתחילים עם נתיבי שיחה בסיסיים

מומלץ לבדוק את הנציג באמצעות הסימולטור בזמן שאתם מבצעים שינויים. לכן, כדאי להתמקד בהתחלה בנתיבי השיחה הבסיסיים בשלב מוקדם של השיחה, ולבדוק את השינויים תוך כדי ביצוע. אחרי שהם יפעלו, אפשר לעבור לנתיבי שיחה מפורטים יותר.

מטפלים במצבים ברמת התהליך לעומת ברמת הדף

כשיוצרים handlers של מצבים, צריך להחליט אם להחיל אותם ברמת התהליך או ברמת הדף. רכיב handler ברמת הרצף נמצא בהיקף בכל פעם שהרצף (ולכן כל דף בתוך הרצף) פעיל. רכיב handler ברמת הדף נמצא בהיקף רק כשהדף הספציפי פעיל. רכיבי handler ברמת התהליך דומים ל-Intent ב-Dialogflow ES ללא הקשר קלט. רכיבי handler ברמת הדף דומים ל-Intent ב-Dialogflow ES עם הקשר קלט.

קוד Webhook

המאפיינים של הבקשה והתגובה של ה-webhook שונים ב-Dialogflow CX. למידע נוסף על תגובות לפעולות לפיתוח API

מחברי ידע

בשלב הזה, Dialogflow CX לא תומך במחברי ידע. תצטרכו להטמיע אותן ככוונות רגילות או להמתין עד ש-Dialogflow CX יתמוך במחברי ידע.

הגדרות הסוכן

בודקים את הגדרות הסוכן ב-Dialogflow ES ומשנים את הגדרות הסוכן ב-Dialogflow CX לפי הצורך.

שימוש בקובץ TODO

כלי ההעברה יוצר קובץ CSV. הפריטים ברשימה הזו מתמקדים בנתונים ספציפיים שדורשים התייחסות. מייבאים את הקובץ הזה לגיליון אלקטרוני. מטפלים בכל פריט בגיליון האלקטרוני, ומשתמשים בעמודה כדי לסמן את השלמת הטיפול.

העברת השימוש ב-API

אם המערכת שלכם משתמשת ב-Dialogflow ES API לקריאות בזמן ריצה או בזמן תכנון, תצטרכו לעדכן את הקוד הזה כדי להשתמש ב-Dialogflow CX API. אם אתם משתמשים רק בקריאות לזיהוי כוונה בזמן הריצה, העדכון הזה אמור להיות די פשוט.

שילובים

אם הנציג משתמש בשילובים, עיינו בקטע השילובים ובצעו שינויים לפי הצורך.

בסעיפי המשנה הבאים מפורטים השלבים המומלצים להעברה.

אימות

כדי לבדוק אם הנציג פועל לפי השיטות המומלצות, אפשר להשתמש באימות הנציג.

בדיקה

בזמן ביצוע שלבי ההעברה הידנית שלמעלה, כדאי לבדוק את הנציג באמצעות הסימולטור. אחרי שהנציג מתחיל לפעול, כדאי להשוות בין השיחות של נציגי Dialogflow ES ו-Dialogflow CX ולוודא שההתנהגות דומה או משופרת.

בזמן בדיקת השיחות האלה באמצעות הסימולטור, כדאי ליצור מקרי בדיקה כדי למנוע רגרסיות בעתיד.

סביבות

בודקים את הסביבות של Dialogflow ES ומעדכנים את הסביבות של Dialogflow CX לפי הצורך.