您在上一步中创建的预构建代理无法提供账号余额等动态数据,因为所有内容都已硬编码到代理中。在本教程的这一步中,您将创建一个可向代理提供动态数据的webhook。本教程中,我们使用 Cloud Run 函数托管 webhook,因为它们简单易用,但您还可以通过许多其他方式托管 webhook 服务。该示例还使用了 Go 编程语言,但您可以使用 Cloud Run functions 支持的任何语言。
创建函数
您可以使用 Google Cloud 控制台创建 Cloud Run 函数(访问文档,打开控制台)。如需为本教程创建函数,请执行以下操作:
请务必将 Dialogflow 代理和函数放在同一项目中。这是 Dialogflow 安全访问您的函数的最简单方法。在创建函数之前,请在 Google Cloud 控制台中选择您的项目。
打开 Cloud Run functions 概览页面。
点击创建函数,然后设置以下字段:
- 环境:第 1 代
- 函数名称:tutorial-banking-webhook
- 区域:如果您为代理指定了区域,请使用相同的区域。
- HTTP 触发器类型:HTTP
- 网址:点击此处的复制按钮,然后保存该值。 您在配置该网络钩子时需要使用此网址。
- 身份验证:需要进行身份验证
- 需要 HTTPS:已选中
点击保存。
点击下一步(您无需特殊的运行时、构建、连接或安全设置)。
设置以下字段:
- 运行时:选择最新的 Go 运行时。
- 源代码:内嵌编辑器
- 入口点:HandleWebhookRequest
将代码替换为以下代码:
package estwh import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "strings" "cloud.google.com/go/spanner" "google.golang.org/grpc/codes" ) // client is a Spanner client, created only once to avoid creation // for every request. // See: https://cloud.google.com/functions/docs/concepts/go-runtime#one-time_initialization var client *spanner.Client func init() { // If using a database, these environment variables will be set. pid := os.Getenv("PROJECT_ID") iid := os.Getenv("SPANNER_INSTANCE_ID") did := os.Getenv("SPANNER_DATABASE_ID") if pid != "" && iid != "" && did != "" { db := fmt.Sprintf("projects/%s/instances/%s/databases/%s", pid, iid, did) log.Printf("Creating Spanner client for %s", db) var err error // Use the background context when creating the client, // but use the request context for calls to the client. // See: https://cloud.google.com/functions/docs/concepts/go-runtime#contextcontext client, err = spanner.NewClient(context.Background(), db) if err != nil { log.Fatalf("spanner.NewClient: %v", err) } } } type queryResult struct { Action string `json:"action"` Parameters map[string]interface{} `json:"parameters"` } type text struct { Text []string `json:"text"` } type message struct { Text text `json:"text"` } // webhookRequest is used to unmarshal a WebhookRequest JSON object. Note that // not all members need to be defined--just those that you need to process. // As an alternative, you could use the types provided by // the Dialogflow protocol buffers: // https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookRequest type webhookRequest struct { Session string `json:"session"` ResponseID string `json:"responseId"` QueryResult queryResult `json:"queryResult"` } // webhookResponse is used to marshal a WebhookResponse JSON object. Note that // not all members need to be defined--just those that you need to process. // As an alternative, you could use the types provided by // the Dialogflow protocol buffers: // https://godoc.org/google.golang.org/genproto/googleapis/cloud/dialogflow/v2#WebhookResponse type webhookResponse struct { FulfillmentMessages []message `json:"fulfillmentMessages"` } // accountBalanceCheck handles the similar named action func accountBalanceCheck(ctx context.Context, request webhookRequest) ( webhookResponse, error) { account := request.QueryResult.Parameters["account"].(string) account = strings.ToLower(account) var table string if account == "savings account" { table = "Savings" } else { table = "Checking" } s := "Your balance is $0" if client != nil { // A Spanner client exists, so access the database. // See: https://pkg.go.dev/cloud.google.com/go/spanner#ReadOnlyTransaction.ReadRow row, err := client.Single().ReadRow(ctx, table, spanner.Key{1}, // The account ID []string{"Balance"}) if err != nil { if spanner.ErrCode(err) == codes.NotFound { log.Printf("Account %d not found", 1) } else { return webhookResponse{}, err } } else { // A row was returned, so check the value var balance int64 err := row.Column(0, &balance) if err != nil { return webhookResponse{}, err } s = fmt.Sprintf("Your balance is $%.2f", float64(balance)/100.0) } } response := webhookResponse{ FulfillmentMessages: []message{ { Text: text{ Text: []string{s}, }, }, }, } return response, nil } // Define a type for handler functions. type handlerFn func(ctx context.Context, request webhookRequest) ( webhookResponse, error) // Create a map from action to handler function. var handlers map[string]handlerFn = map[string]handlerFn{ "account.balance.check": accountBalanceCheck, } // handleError handles internal errors. func handleError(w http.ResponseWriter, err error) { log.Printf("ERROR: %v", err) http.Error(w, fmt.Sprintf("ERROR: %v", err), http.StatusInternalServerError) } // HandleWebhookRequest handles WebhookRequest and sends the WebhookResponse. func HandleWebhookRequest(w http.ResponseWriter, r *http.Request) { var request webhookRequest var response webhookResponse var err error // Read input JSON if err = json.NewDecoder(r.Body).Decode(&request); err != nil { handleError(w, err) return } log.Printf("Request: %+v", request) // Get the action from the request, and call the corresponding // function that handles that action. action := request.QueryResult.Action if fn, ok := handlers[action]; ok { response, err = fn(r.Context(), request) } else { err = fmt.Errorf("Unknown action: %s", action) } if err != nil { handleError(w, err) return } log.Printf("Response: %+v", response) // Send response if err = json.NewEncoder(w).Encode(&response); err != nil { handleError(w, err) return } }
点击部署。
等待状态指示器显示函数已成功部署。在等待期间,检查您刚刚部署的代码。
为您的代理配置网络钩子
现在,网络钩子已作为服务存在,您需要将此网络钩子与您的代理相关联。这是通过执行方式完成的。如需为您的代理启用和管理 fulfillment,请执行以下操作:
- 前往 Dialogflow ES 控制台。
- 选择您刚刚创建的预构建代理。
- 选择左侧边栏菜单中的 Fulfillment。
- 将网路钩子字段切换为已启用。
- 提供您从上方复制的网址。 将其他所有字段留空。
- 点击页面底部的保存。
现在,为代理启用了 fulfillment,您需要为 intent 启用 fulfillment:
- 在左侧边栏菜单中选择意图 (Intents)。
- 选择 account.balance.check intent。
- 向下滚动到 Fulfillment 部分。
- 开启为此意图启用 webhook 调用。
- 点击保存。
试用代理
您的客服人员现在可以开始试用了。 点击测试代理按钮以打开模拟器。尝试与客服人员进行以下对话:
对话回合 | 您 | 代理 |
---|---|---|
1 | 您好! | 您好!感谢您选择 ACME 银行。 |
2 | 我想知道我的账号余额 | 您希望将余额存入哪个账户:储蓄账户还是支票账户? |
3 | 正在检查 | 您的最新余额为:0.00 美元 |
在第 3 轮对话中,您提供了“支票账户”作为账号类型。account.balance.check intent 有一个名为 account 的参数。在此对话中,此参数设置为“checking”。 该 intent 的操作值也为“account.balance.check”。系统会调用网络钩子服务,并将参数和操作值传递给该服务。
如果您查看上面的 webhook 代码,就会发现此操作会触发调用同名函数。该函数用于确定账号余额。该函数会检查是否已使用连接到数据库的信息设置特定环境变量。如果未设置这些环境变量,该函数将使用硬编码的账号余额。在后续步骤中,您将更改函数的环境,以便它从数据库中检索数据。
问题排查
该 webhook 代码包含日志记录语句。如果您遇到问题,请尝试查看函数的日志。
更多信息
如需详细了解上述步骤,请参阅: