相較於 Dialogflow ES 代理,Dialogflow CX 代理提供更強大的對話控制項和工具。如果 Dialogflow ES 代理程式會處理複雜的對話,建議您改用 Dialogflow CX。
本指南說明如何將代理從 Dialogflow ES 遷移至 Dialogflow CX。這兩種代理有許多根本差異,因此無法直接遷移。
如果您使用本指南進行遷移,請按一下上方的「Send feedback」(提供意見) 按鈕,提供正面或負面意見。我們會根據這些意見回饋,持續改善這份指南。
大致上來說,建議採用自動化/手動混合程序。您將使用一項工具,讀取部分 Dialogflow ES 代理資料,將資料寫入 Dialogflow CX 代理,並擷取待辦事項清單。接著,您可以使用最佳做法、待辦事項清單和工具遷移的資料,重新建立完整的 Dialogflow CX 代理。
瞭解 Dialogflow CX
嘗試這項遷移作業前,請務必充分瞭解 Dialogflow CX 流程的運作方式。您可以從這裡開始:
您也應該詳閱其他概念文件,瞭解新代理可能需要的功能。請著重於下列事項:
瞭解 Dialogflow ES 和 Dialogflow CX 的差異
本節列出 Dialogflow ES 和 Dialogflow CX 之間最重要的差異。稍後執行手動遷移步驟時,請參閱本節的指引。
結構和對話路徑控制
Dialogflow ES 提供下列功能,可控管結構和對話路徑:
- 意圖是代理的建構元素。在對話中的任何時間點,系統都會比對意圖,從某種意義上來說,每個意圖都是對話的節點。
- Context 用於控制對話。情境可用來控管在任何指定時間可比對的意圖。上下文會在一定數量的對話回合後失效,因此這類控制項在長對話中可能不準確。
Dialogflow CX 提供結構資源階層,並可更精確地控制對話路徑:
- 頁面是對話的圖表節點。Dialogflow CX 對話類似於狀態機。在對話的任何時間點,只有一個頁面會處於有效狀態。對話可能會根據使用者輸入內容或事件,轉換至其他頁面。一個頁面通常會維持啟用狀態,直到多個對話回合結束。
- 流程是一組相關頁面。每個流程都應處理高階對話主題。
- 狀態處理常式用於控管轉換和回覆。狀態處理常式分成三種類型:
- 意圖路徑:包含必須比對的意圖、選用回覆和選用頁面轉換。
- 條件路徑:包含必須符合的條件、選用回覆和選用頁面轉換。
- 事件處理常式:包含必須叫用的事件名稱、選用回應和選用頁面轉換。
- 範圍用於控制是否可以呼叫狀態處理常式。大多數處理常式都與頁面或整個流程相關聯。如果相關聯的頁面或流程處於有效狀態,則處理常式在範圍內,可以呼叫。Dialogflow CX 意圖路徑在範圍內,類似於 Dialogflow ES 意圖,且輸入背景資訊處於啟用狀態。
設計代理的流程和頁面時,請務必瞭解代理設計指南流程部分的建議。
填寫表單
Dialogflow ES 會使用空位填補功能,向使用者收集必要參數:
- 這些參數是標示為必要的意圖參數。
- 系統會持續比對意圖,直到收集到所有必要參數。
- 您可以定義提示,要求使用者提供值。
Dialogflow CX 會使用表單填寫功能,向使用者收集必要參數:
轉換
當使用者輸入內容與意圖相符時,Dialogflow ES 會自動從一個意圖轉換到下一個意圖。只有沒有輸入背景資訊的意圖,或具有有效輸入背景資訊的意圖,才能進行這項比對。
當範圍內的狀態處理常式符合要求,並提供轉換目標時,Dialogflow CX 就會從一個頁面轉換到下一個頁面。您可以使用這些轉換,可靠地引導使用者完成對話。您可以透過多種方式控制這些轉換:
- 意圖比對成功時,可觸發意圖路徑。
- 滿足條件即可觸發條件路由。
- 叫用事件可觸發事件處理常式。
- 如果使用者多次嘗試後仍無法提供值,重新提示處理常式可能會導致轉換。
- 您可以使用符號轉場目標做為轉場目標。
代理回覆
意圖相符時,Dialogflow ES 代理的回覆會傳送給使用者:
- 服務專員可以從可能的回覆清單中選取一則訊息做為回覆。
- 回覆可以因平台而異,並使用多元回覆格式。
- 回覆可由 Webhook 驅動。
呼叫執行要求時,系統會將 Dialogflow CX 代理的回覆傳送給使用者。Dialogflow ES 執行要求一律會涉及 Webhook,但 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、fulfillment 參數預設值和 API 呼叫設定。
- 在回覆、參數提示、重新提示處理常式、參數預設值和 Webhook 程式碼中參照:
- 工作階段參數的參照格式為
$session.params.parameter-id,意圖參數則為$intent.params.parameter-id。 - 意圖參數參照支援
.original和.resolved後置字串語法。工作階段參數不支援這個語法。
- 工作階段參數的參照格式為
系統實體
Dialogflow ES 支援許多系統實體。
Dialogflow CX 支援許多相同的系統實體,但仍有一些差異。遷移時,請確認 Dialogflow CX 也支援您在 Dialogflow ES 中使用的系統實體,且語言相同。如果沒有,請為這些實體建立自訂實體。
活動
Dialogflow ES 事件具有下列特性:
- 可透過 API 呼叫或 Webhook 叫用,以比對意圖。
- 可設定參數。
- 整合式廣告平台會叫用少數事件。
Dialogflow CX 事件具有下列特性:
- 可透過 API 呼叫或 Webhook 叫用,以呼叫事件處理常式。
- 無法設定參數。
- 許多內建事件可用於處理使用者未輸入內容、使用者輸入內容無法辨識、Webhook 導致參數失效,以及 Webhook 錯誤等情況。
- 叫用作業可由與其他狀態處理常式相同的範圍規則控制。
內建意圖
Dialogflow ES 支援下列內建意圖:
以下說明 Dialogflow CX 對內建意圖的支援:
- 支援歡迎意圖。
- 不提供備用意圖。請改用事件處理常式中的 no-match 事件。
- 如要提供負面範例,請使用預設負面意圖。
- 系統不提供預先定義的後續意圖。您必須依據代理的需求建立這些意圖。舉例來說,您可能需要建立意圖,處理對代理問題的否定回答 (「不」、「不用了,謝謝」、「不,我不要」等)。Dialogflow CX 意圖可在代理中重複使用,因此只需定義一次。在不同範圍內,為這些常見意圖使用不同的意圖路徑,可大幅提升對話控制能力。
Webhook
Dialogflow ES Webhook 具有下列特性:
- 您可以為代理設定一項 Webhook 服務。
- 每個意圖都可以標示為使用 Webhook。
- 沒有內建支援處理 Webhook 錯誤。
- Webhook 會使用意圖動作或意圖名稱,判斷呼叫來源在代理中的位置。
- 控制台提供直接修改。
Dialogflow CX Webhook 具有下列特性:
- 您可以為代理設定多個 Webhook 服務。
- 每個 fulfillment 都能選擇指定 Webhook 呼叫。
- 內建支援 Webhook 錯誤處理。
- Dialogflow CX 執行要求 Webhook 包含標記。這個標記與 Dialogflow ES 動作類似,但僅用於呼叫 Webhook。Webhook 服務可使用這些標記,判斷代理中呼叫 Webhook 的位置。
- 控制台沒有內建的 Webhook 程式碼編輯器。通常會使用 Cloud Run functions,但也有許多其他選項。
遷移至 Dialogflow CX 時,您必須變更 Webhook 程式碼,因為要求和回應屬性不同。
整合
Dialogflow ES 整合和 Dialogflow CX 整合支援不同平台。如果平台同時支援兩種代理程式類型,設定可能會有差異。
如果 Dialogflow CX 不支援您使用的 Dialogflow ES 整合項目,您可能需要切換平台或自行導入整合項目。
更多 Dialogflow CX 專屬功能
還有許多其他功能僅由 Dialogflow CX 提供。建議您在遷移時使用這些功能。例如:
- 精確自然語言理解
- 進階語音設定 (語音感測結束處、無語音逾時等)
- 變更記錄
- 條件式邏輯
- 電話整合的 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 不支援的系統實體,就會建立待辦事項,並假設預設語言為英文,且使用美國區域。系統實體支援情形因語言和地區而異。如需其他語言和地區,請修改工具來執行這項檢查。
重要遷移步驟
以下各小節將概略說明應採取的遷移步驟。您不一定要按照順序執行這些手動步驟,甚至可能需要同時或以不同順序執行。請先詳閱步驟並規劃變更,再實際進行。
執行遷移工具後,即可重建 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 CX fulfillment 訊息,類似於 Dialogflow ES 意圖回覆。這個頁面可能有多個意圖路徑。原始頁面處於啟用狀態時,這些意圖路徑可將對話轉換為多種可能路徑。許多 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 標記 | 請參閱 Webhook。 |
| 參數 | 意圖參數和/或網頁表單參數 | 已由工具遷移至意圖參數。如果需要參數,工具會建立待辦事項,可能遷移至頁面。請參閱參數。 |
| 參數提示 | 頁面表單參數提示 | 請參閱「填寫表單」。 |
建立工作流程
為每個高階對話主題建立流程。每個流程中的主題應有所區別,以免對話頻繁在流程之間來回跳轉。
如果您使用的是超級代理,每個子代理應成為一或多個流程。
從基本對話路徑開始
建議您在反覆調整變更時,使用模擬器測試代理。因此,您一開始應著重於對話初期的基本對話路徑,並在變更時進行測試。確認這些功能正常運作後,再繼續設計更詳細的對話路徑。
流程層級與頁面層級狀態處理常式
建立狀態處理常式時,請考量應在流程層級或頁面層級套用。只要流程 (以及流程中的任何頁面) 處於有效狀態,流程層級處理常式就會在範圍內。只有在特定頁面處於啟用狀態時,頁面層級處理常式才會在範圍內。流程層級處理常式與 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 環境。