與 Dialogflow ES 虛擬服務專員相比,Dialogflow CX 虛擬服務專員提供更強大的對話控制項和工具。如果 Dialogflow ES 服務專員處理複雜的對話,建議遷移至 Dialogflow CX。
本指南說明如何將虛擬服務專員從 Dialogflow ES 遷移至 Dialogflow CX。這兩種代理程式類型有許多基本差異,因此無法直接執行這項遷移作業。
如果您使用本指南進行遷移,請點選上方的「提供意見」按鈕,提供正面或負面意見回饋。我們會根據這些意見回饋,持續改善本指南。
大致上來說,建議的程序是自動/手動混合程序。您將使用工具讀取部分 Dialogflow ES 服務專員資料、將資料寫入 Dialogflow CX 服務專員,並擷取待辦事項清單。然後,使用最佳做法、待辦事項清單和工具遷移的資料,重新建立完整的 Dialogflow CX 虛擬服務專員。
瞭解 Dialogflow CX
嘗試遷移之前,請先充分瞭解 Dialogflow CX 流程的運作方式。你可以從以下連結開始:
此外,您也應詳閱其他概念文件,瞭解新代理程式可能需要的功能。請著重於下列事項:
瞭解 Dialogflow ES/Dialogflow CX 的差異
本節列出 Dialogflow ES 和 Dialogflow CX 之間最重要的差異。稍後執行手動遷移步驟時,請參閱本節的指引。
結構和對話路徑控制
Dialogflow ES 提供下列項目,用於控制結構和對話路徑:
- 意圖是代理程式的構成要素。在對話的任何時間點,系統都會比對意圖,從某種意義上來說,每個意圖都是對話的節點。
- 背景資訊可用來控制對話。背景資訊可用來控管在任何時間可比對的意圖。 背景資訊會在特定次數的對話輪替後過期,因此這類控制項可能無法準確掌握長時間的對話。
Dialogflow CX 提供結構資源階層,並可更精確地控制對話路徑:
- 頁面 是對話的圖表節點。 Dialogflow CX 對話與狀態機相似。在對話的任何時間點,都只有一個頁面處於啟用狀態。根據使用者的輸入內容或事件,對話可能會轉移到其他頁面。網頁通常會在多個對話回合中保持啟用狀態。
- 流程 是指一組相關頁面。 每個流程都應處理高層級的對話主題。
- 狀態處理常式
用於控制轉場效果和回應。
狀態處理常式有三種類型:
- 意圖路徑:包含必須相符的意圖、選用回應和選用頁面轉場效果。
- 條件路徑:包含必須符合的條件、選用回應和選用頁面轉換。
- 事件處理常式:包含必須叫用的事件名稱、選用回應和選用頁面轉場效果。
- 範圍:用於控制是否可呼叫狀態處理常式。大多數處理常式都與網頁或整個流程相關聯。 如果相關聯的網頁或流程處於啟用狀態,處理常式就會在範圍內,且可以呼叫。範圍內的 Dialogflow CX 意圖路徑,類似於具有啟用輸入背景資訊的 Dialogflow ES 意圖。
設計代理程式的流程和頁面時,請務必瞭解代理程式設計指南的流程部分所提供的建議。
填寫表單
Dialogflow ES 會使用運算單元填充,向使用者收集必要參數:
- 這些參數是標示為必填的意圖參數。
- 系統會持續比對意圖,直到收集到所有必要參數為止。
- 您可以定義提示,要求使用者提供值。
Dialogflow CX 會使用表單填寫功能,向使用者收集必要參數:
- 這些參數與網頁相關聯,且會在網頁處於啟用狀態時收集。
- 您可以使用網頁的條件路徑,判斷表單是否填寫完成。這些條件路徑通常會轉換至其他網頁。
- 您可以定義提示和重新提示處理常式,妥善處理多次嘗試收集值的作業。
轉場效果
當使用者輸入內容與意圖相符時,Dialogflow ES 會自動從一個意圖轉換到下一個意圖。只有沒有輸入背景資訊的意圖,或有處於「使用中」狀態的輸入背景資訊的意圖,才會發生這種比對。
當範圍內的狀態處理常式滿足需求並提供轉移目標時,Dialogflow CX 會從一個頁面轉移至下一個頁面。您可以使用這些轉場效果,可靠地引導使用者進行對話。您可以透過多種方式控制這些轉場效果:
- 意圖比對可以觸發意圖路徑。
- 滿足條件即可觸發條件路徑。
- 事件的叫用作業可以觸發事件處理常式。
- 如果使用者多次嘗試後仍無法提供值,重新提示處理常式可能會導致轉移。
- 您可以使用符號轉換目標做為轉換目標。
服務專員回覆
比對出相符意圖時,Dialogflow ES 虛擬服務專員會將回應傳送給使用者:
- 代理程式可以從可能的回答清單中,選取一則訊息做為回覆。
- 回覆內容可以因平台而異,並使用豐富的回覆格式。
- 回覆內容可由 Webhook 觸發。
呼叫執行要求時,系統會將 Dialogflow CX 服務專員的回應傳送給使用者。與一律會涉及 Webhook 的 Dialogflow ES 執行要求不同,Dialogflow CX 執行要求可能會呼叫 Webhook,也可能不會,具體取決於執行要求資源是否已設定 Webhook。靜態和動態回應都是根據 Webhook 回應產生,並由執行要求控制。建立代理程式回覆的方式有很多種:
- 執行要求可提供給任何類型的狀態處理常式。
- 在對話輪次中,可以透過回應佇列串聯多個回應。在某些情況下,這項功能可簡化代理程式設計。
- Dialogflow CX 不支援內建的平台專屬回覆。 不過,這項功能提供多種回應類型,包括可用於平台專屬回應的自訂酬載。
參數
Dialogflow ES 參數具有下列特性:
- 只能在意圖中定義。
- 由使用者輸入、事件、Webhook 和 API 呼叫設定。
- 在回應、參數提示、Webhook 程式碼和參數值中參照:
- 基本參照格式為
$parameter-name。 - 參照支援
.original、.partial和.recent後置字串語法。 - 參照可以指定有效內容:
#context-name.parameter-name。 - 參照可指定事件參數:
#event-name.parameter-name。
- 基本參照格式為
Dialogflow CX 參數具有下列特性:
- 在意圖和頁面表單中定義。
- 意圖和表單參數會傳播至工作階段參數,在工作階段期間可供參照。
- 由使用者輸入、Webhook、預設的完成參數和 API 呼叫設定。
- 在回應、參數提示、重新提示處理常式、參數預設值和 Webhook 程式碼中參照:
- 工作階段參數的參照格式為
$session.params.parameter-id,意圖參數的參照格式為$intent.params.parameter-id。 - 意圖參數參照支援
.original和.resolved後置字串語法。 工作階段參數不支援這種語法。
- 工作階段參數的參照格式為
系統實體
Dialogflow ES 支援多種系統實體。
Dialogflow CX 支援許多相同的系統實體,但仍有一些差異。遷移時,請確認 Dialogflow ES 中使用的系統實體,也支援相同語言的 Dialogflow CX。如果沒有,您應為這些項目建立自訂實體。
活動
Dialogflow ES 事件具有下列特性:
- 可從 API 呼叫或 Webhook 叫用,以比對意圖。
- 可以設定參數。
- 整合平台會叫用少量事件。
Dialogflow CX 事件具有下列特性:
- 可透過 API 呼叫或 Webhook 叫用,呼叫事件處理常式。
- 無法設定參數。
- 許多內建事件可用於處理缺少使用者輸入內容、無法辨識使用者輸入內容、Webhook 導致參數無效,以及 Webhook 錯誤等情況。
- 與其他狀態處理常式一樣,您可以使用相同的範圍規則控制叫用。
內建意圖
Dialogflow ES 支援下列內建意圖:
以下說明 Dialogflow CX 對內建意圖的支援:
- 支援歡迎意圖。
- 系統未提供備用意圖。 請改用事件處理常式中的不相符事件。
- 如為排除範例,請使用預設排除意圖。
- 系統不會提供預先定義的後續追蹤意圖。 您必須根據代理程式需求建立這些意圖。 舉例來說,您可能需要建立意圖,處理對代理程式問題的否定回答 (例如「不要」、「不用了」、「我沒有」等等)。Dialogflow CX 意圖可在代理程式中重複使用,因此您只需定義一次。針對這些常見意圖,在不同範圍使用不同意圖路徑,可大幅提升對話控制權。
Webhook
Dialogflow ES Webhook 具有下列特性:
- 您可以為代理程式設定一項 Webhook 服務。
- 每個意圖都可以標示為使用 Webhook。
- 系統未內建處理 Webhook 錯誤的支援。
- Webhook 會使用意圖動作或意圖名稱,判斷呼叫代理程式的位置。
- 控制台提供內嵌編輯器。
Dialogflow CX 網路鉤子具有下列特性:
- 您可以為代理程式設定多個 Webhook 服務。
- 每個完成動作都可以選擇指定 Webhook 呼叫。
- 系統內建網頁掛鉤錯誤處理支援功能。
- Dialogflow CX 執行要求 Webhook 包含標記。這個標記與 Dialogflow ES 動作類似, 但只會在呼叫 Webhook 時使用。 Webhook 服務可以使用這些標記,判斷呼叫代理程式的位置。
- 主控台沒有內建的 Webhook 程式碼編輯器。 一般來說,您會使用 Cloud Run 函式,但也有許多其他選項。
遷移至 Dialogflow CX 時,您需要變更 Webhook 程式碼,因為要求和回應屬性不同。
整合
Dialogflow ES 整合和 Dialogflow CX 整合支援不同的平台。如果平台同時支援這兩種代理程式類型,設定方式可能會有所不同。
如果 Dialogflow CX 不支援您使用的 Dialogflow ES 整合服務,您可能需要切換平台或自行實作整合服務。
僅限 Dialogflow CX 的功能
Dialogflow CX 還提供許多其他功能。建議您在遷移期間使用這些功能。 例如:
- 進階 NLU
- 進階語音設定 (語音感測結束處、無語音逾時等)
- 變更記錄
- 條件式邏輯
- 電話整合的 DTMF 輸入
- 環境專屬的 Webhook
- 實驗
- 路徑群組
- 搜尋代理程式資料
- 安全性設定 (遮蓋和資料保留)
- 系統函式,可提供進階回覆和條件
- 測試案例
- 驗證代理程式資料
最佳做法
遷移前,請先熟悉 Dialogflow CX 虛擬服務專員設計最佳做法。這些 Dialogflow CX 最佳做法與 Dialogflow ES 最佳做法有許多相似之處,但也有一些是 Dialogflow CX 獨有的。
關於遷移工具
遷移工具會將大部分的 Dialogflow ES 資料複製到 Dialogflow CX 虛擬服務專員,並寫入 TODO 檔案,列出必須手動遷移的項目。這項工具只會複製自訂實體類型和意圖訓練詞組。 建議您根據自身需求自訂這項工具。
遷移工具代碼
以下是工具的程式碼。 您應檢查這項工具的程式碼,瞭解其用途。您可能想變更這段程式碼,以便處理代理程式中的特定情況。 您將在下列步驟中執行這項工具。
// 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 不支援的系統實體,就會建立 TODO 項目, 並假設預設語言為英文,且使用美國區域。 系統實體支援情形因語言和地區而異。 如要檢查其他語言和區域,請修改工具。
必要遷移步驟
以下各小節將概略說明遷移步驟。您不必按照順序執行這些手動步驟,甚至可能需要同時執行這些步驟,或以不同順序執行。請先詳閱步驟,並開始規劃變更,再實際進行變更。
執行遷移工具後,您可以重建 Dialogflow CX 虛擬服務專員。您仍有相當多的遷移工作要做,但手動輸入的大部分資料都會出現在 Dialogflow CX 代理程式和 TODO 檔案中。
建立 Dialogflow CX 虛擬服務專員
如果您尚未建立 Dialogflow CX 虛擬服務專員,請先完成這項作業。請務必使用與 Dialogflow ES 虛擬服務專員相同的預設語言。
執行遷移工具
請按照下列步驟執行這項工具:
- 如果尚未安裝,請在電腦上安裝 Go。
- 建立名為
migrate的工具程式碼目錄。 - 將上述工具程式碼複製到這個目錄中名為
main.go的檔案。 - 視需要修改程式碼,以符合您的情況。
在這個目錄中建立 Go 模組。例如:
go mod init migrate安裝 Dialogflow ES V2 和 Dialogflow CX V3 Go 用戶端程式庫:
go get cloud.google.com/go/dialogflow/apiv2 go get cloud.google.com/go/dialogflow/cx/apiv3確認您已設定用戶端程式庫驗證。
執行這項工具,並將輸出內容儲存至檔案:
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 錯誤:訓練詞組部分提及意圖未定義的參數。 | 如果您先前使用 Dialogflow ES API 建立意圖參數時,與訓練詞組不一致,就可能發生這種情況。如要修正這個問題,請從控制台重新命名 Dialogflow ES 參數,確認訓練詞組正確使用該參數,然後按一下「儲存」。如果訓練詞組參照不存在的參數,也可能發生這種情況。 |
修正錯誤後,您必須先清除 Dialogflow CX 虛擬服務專員的意圖和實體,才能再次執行遷移工具。
將 Dialogflow ES 意圖資料移至 Dialogflow CX
這項工具會將意圖訓練詞組和參數遷移至 Dialogflow CX 意圖,但還有許多其他 Dialogflow ES 意圖欄位需要手動遷移。
Dialogflow ES 意圖可能需要對應的 Dialogflow CX 頁面、對應的 Dialogflow CX 意圖,或兩者皆是。
如果使用 Dialogflow ES 意圖比對,將對話從特定對話節點轉移至另一個節點,您應該在代理程式中擁有與此意圖相關的兩個頁面:
- 含有意圖路徑的原始頁面,會轉換至下一個頁面:原始頁面中的意圖路徑可能含有類似於 Dialogflow ES 意圖回應的 Dialogflow CX 實現訊息。這個頁面中可能有多個意圖路徑。 在原始網頁處於有效狀態時,這些意圖路徑可將對話轉移至許多可能路徑。許多 Dialogflow ES 意圖會共用相同的對應 Dialogflow CX 原始頁面。
- 下一頁,也就是原始頁面中意圖路徑的轉移目標: 下一頁的 Dialogflow CX 進入條件可能包含類似於 Dialogflow ES 意圖回應的 Dialogflow CX 條件訊息。
如果 Dialogflow ES 意圖包含必要參數,您應建立對應的 Dialogflow CX 頁面,並在表單中加入相同參數。
Dialogflow CX 意圖和 Dialogflow CX 頁面通常會共用相同的參數清單,這表示單一 Dialogflow ES 意圖會對應至 Dialogflow CX 頁面和 Dialogflow CX 意圖。當意圖路徑中含有參數的 Dialogflow CX 意圖相符時,對話通常會轉換至含有相同參數的頁面。從意圖比對結果擷取的參數會傳播至工作階段參數,可用於部分或完整填寫網頁表單參數。
Dialogflow CX 沒有備用意圖和預先定義的後續追蹤意圖。 請參閱內建意圖。
下表說明如何將 Dialogflow ES 的特定意圖資料對應至 Dialogflow CX 資源:
| Dialogflow ES 意圖資料 | 對應的 Dialogflow CX 資料 | 敬請採取行動 |
|---|---|---|
| 訓練詞組 | 意圖訓練詞組 | 由工具遷移。這項工具會檢查系統實體支援,並為不支援的系統實體建立待辦事項。 |
| 服務專員回覆 | 執行要求的回應訊息 | 請參閱代理程式回應。 |
| 對話控制項的背景資訊 | 無 | 請參閱「結構和對話路徑控制」。 |
| Webhook 設定 | 執行要求 Webhook 設定 | 請參閱「Webhook」。 |
| 活動 | 流程層級或網頁層級事件處理常式 | 請參閱活動。 |
| 動作 | 執行要求 Webhook 標記 | 請參閱網頁掛鉤。 |
| 參數 | 意圖參數和/或頁面表單參數 | 已透過工具遷移至意圖參數。如果需要參數,工具會建立 TODO 項目,以便可能遷移至頁面。請參閱參數。 |
| 參數提示 | 網頁表單參數提示 | 請參閱「填寫表單」。 |
建立流程
為每個高階對話主題建立流程。 每個流程的主題應有所區別,以免對話在流程之間來回跳動。
如果您使用巨型代理程式,每個子代理程式都應成為一或多個流程。
從基本對話路徑著手
在疊代變更時,最好使用模擬工具測試代理程式。 因此,您應先專注於對話初期的基本對話路徑,並在進行變更時測試。確認這些路徑運作正常後,即可開始建立更詳細的對話路徑。
流程層級與網頁層級狀態處理常式
建立狀態處理常式時,請考慮應在流程層級或網頁層級套用。只要流程 (以及流程中的任何網頁) 處於有效狀態,流程層級處理常式就會在範圍內。只有在特定頁面處於啟用狀態時,頁面層級處理常式才會在範圍內。 流程層級處理常式與沒有輸入背景資訊的 Dialogflow ES 意圖類似。 網頁層級處理常式與具有輸入背景資訊的 Dialogflow ES 意圖類似。
Webhook 代碼
Dialogflow CX 的 Webhook 要求和回應屬性有所不同。 請參閱Webhook 一節。
知識連接器
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 環境。