网络钩子服务

如需在生产系统中使用 fulfillment,您应实现并部署网络钩子服务。如需处理 fulfillment,您的网络钩子服务需要接受 JSON 请求并返回本指南中指定的 JSON 响应。fulfillment 概览文档中介绍了关于 fulfillment 和网络钩子的详细处理流程。

网络钩子服务要求

您的网络钩子服务必须满足以下要求:

  • 它必须处理 HTTPS 请求。不支持 HTTP。如果您使用计算无服务器计算解决方案在 Google Cloud Platform 上托管网络钩子服务,请参阅使用 HTTPS 服务的产品文档。对于其他托管项,请参阅获取您网域的 SSL 证书
  • 其请求的网址须可公开访问。
  • 它必须使用 JSON WebhookRequest 正文处理 POST 请求。
  • 它必须使用 JSON WebhookResponse 正文来响应 WebhookRequest 请求。

身份验证

请务必保护您的网络钩子服务,确保只有您或您的 Dialogflow 代理才有权发出请求。Dialogflow 支持以下身份验证机制:

网络钩子请求

当为 fulfillment 配置的意图匹配时,Dialogflow 会向您的网络钩子服务发送 HTTPS POST 网络钩子请求。此请求的正文是一个 JSON 对象,其中包含有关匹配意图的信息。

除了最终用户查询之外,许多集成还会发送一些有关最终用户的信息。例如,用于唯一标识用户的 ID。您可以通过网络钩子请求中的 originalDetectIntentRequest 字段访问此信息,此信息包含从集成平台发送的信息。

如需了解详情,请参阅 WebhookRequest 参考文档。

以下是请求示例:

{
  "responseId": "response-id",
  "session": "projects/project-id/agent/sessions/session-id",
  "queryResult": {
    "queryText": "End-user expression",
    "parameters": {
      "param-name": "param-value"
    },
    "allRequiredParamsPresent": true,
    "fulfillmentText": "Response configured for matched intent",
    "fulfillmentMessages": [
      {
        "text": {
          "text": [
            "Response configured for matched intent"
          ]
        }
      }
    ],
    "outputContexts": [
      {
        "name": "projects/project-id/agent/sessions/session-id/contexts/context-name",
        "lifespanCount": 5,
        "parameters": {
          "param-name": "param-value"
        }
      }
    ],
    "intent": {
      "name": "projects/project-id/agent/intents/intent-id",
      "displayName": "matched-intent-name"
    },
    "intentDetectionConfidence": 1,
    "diagnosticInfo": {},
    "languageCode": "en"
  },
  "originalDetectIntentRequest": {}
}

网络钩子响应

一旦网络钩子收到网络钩子请求,其需要发送一个网络钩子响应。此响应的正文为 JSON 对象,其包含以下信息:

您的响应会受到以下限制:

  • 对于 Google 助理应用,响应必须在 10 秒内发生;对于其他所有应用,响应必须在 5 秒内发生,否则请求会超时。
  • 响应大小不得超过 64 KiB。

如需了解详情,请参阅 WebhookResponse 参考文档。

文本响应

文本响应示例:

{
  "fulfillmentMessages": [
    {
      "text": {
        "text": [
          "Text response from webhook"
        ]
      }
    }
  ]
}

卡片响应

卡片响应示例:

{
  "fulfillmentMessages": [
    {
      "card": {
        "title": "card title",
        "subtitle": "card text",
        "imageUri": "https://example.com/images/example.png",
        "buttons": [
          {
            "text": "button text",
            "postback": "https://example.com/path/for/end-user/to/follow"
          }
        ]
      }
    }
  ]
}

Google 助理响应

Google 助理响应示例:

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "richResponse": {
        "items": [
          {
            "simpleResponse": {
              "textToSpeech": "this is a Google Assistant response"
            }
          }
        ]
      }
    }
  }
}

Context

设置输出上下文的示例:

{
  "fulfillmentMessages": [
    {
      "text": {
        "text": [
          "Text response from webhook"
        ]
      }
    }
  ],
  "outputContexts": [
    {
      "name": "projects/project-id/agent/sessions/session-id/contexts/context-name",
      "lifespanCount": 5,
      "parameters": {
        "param-name": "param-value"
      }
    }
  ]
}

Event

调用自定义事件的示例:

{
  "followupEventInput": {
    "name": "event-name",
    "languageCode": "en-US",
    "parameters": {
      "param-name": "param-value"
    }
  }
}

会话实体

设置会话实体的示例:

{
  "fulfillmentMessages": [
    {
      "text": {
        "text": [
          "Choose apple or orange"
        ]
      }
    }
  ],
  "sessionEntityTypes":[
    {
      "name":"projects/project-id/agent/sessions/session-id/entityTypes/fruit",
      "entities":[
        {
          "value":"APPLE_KEY",
          "synonyms":[
            "apple",
            "green apple",
            "crabapple"
          ]
        },
        {
          "value":"ORANGE_KEY",
          "synonyms":[
            "orange"
          ]
        }
      ],
      "entityOverrideMode":"ENTITY_OVERRIDE_MODE_OVERRIDE"
    }
  ]
}

启用和管理 fulfillment

如需通过控制台为您的代理启用和管理 fulfillment,请执行以下操作:

  1. 转到 Dialogflow ES 控制台
  2. 选择一个代理。
  3. 选择左侧边栏菜单中的 Fulfillment
  4. 网路钩子字段切换为已启用
  5. 在表单中提供网络钩子服务的详细信息。如果您的网络钩子不需要身份验证,请将身份验证字段留空。
  6. 点击页面底部的保存

启用 fulfillment 的屏幕截图。

要使用 API 为代理启用和管理 fulfillment,请参阅代理参考getFulfillmentupdateFulfillment 方法可用于管理 fulfillment 设置。

如需通过控制台为意图启用 fulfillment,请执行以下操作:

  1. 在左侧边栏菜单中选择意图 (Intents)。
  2. 选择一个意图。
  3. 向下滚动到 Fulfillment 部分。
  4. 开启为此意图启用 webhook 调用
  5. 点击保存

如需使用 API 为意图启用 fulfillment,请参阅意图参考。将 webhookState 字段设置为 WEBHOOK_STATE_ENABLED

网络钩子错误

如果您的网络钩子服务发生错误,则会返回以下某个 HTTP 状态代码:

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not found
  • 500 Server fault
  • 503 Service Unavailable

在以下任何一种错误情况下,Dialogflow 都会使用为当前匹配的意图配置的内置响应来响应最终用户:

  • 响应已超时。
  • 收到错误状态代码。
  • 无效响应。
  • 网络钩子服务不可用。

此外,如果意图匹配由检测意图 API 调用触发,则检测意图响应中的 status 字段包含网络钩子错误信息。例如:

"status": {
    "code": 206,
    "message": "Webhook call failed. <details of the error...>"
}

使用 Cloud Functions

您可以通过多种方式使用 Cloud Functions 进行履行。Dialogflow 内嵌编辑器Cloud Functions 集成。使用内嵌编辑器创建和修改网络钩子代码时,Dialogflow 会与 Cloud Functions 函数建立安全连接。

您还可以选择使用内嵌编辑器创建的 Cloud Functions 函数(可能是因为您想要使用 Node.js 以外的语言)。如果 Cloud Functions 函数与您的代理位于同一项目中,则代理可以调用您的网络钩子,而无需任何特殊配置。

但是,在下面两种情况下,您必须手动设置此集成:

  1. 您的代理项目必须存在具有以下地址的 Dialogflow Service Agent 服务帐号
    service-agent-project-number@gcp-sa-dialogflow.iam.gserviceaccount.com
    当您为项目创建第一个代理时,通常会自动创建这个特殊的服务帐号及关联的密钥。如果您的代理是 2021 年 5 月 10 日之前创建的,则您可能需要使用以下代码触发创建此特殊服务帐号:
    1. 为项目创建新的代理。
    2. 执行以下命令:
      gcloud beta services identity create --service=dialogflow.googleapis.com --project=agent-project-id
  2. 如果网络钩子函数位于与代理不同的项目中,则必须提供 Cloud Functions Invoker IAM 角色 Dialogflow 服务代理服务帐号。

服务身份令牌

Dialogflow 调用网络钩子时,会通过请求提供 Google 身份令牌。任何网络钩子都可以选择性地使用 Google 客户端库或 github.com/googleapis/google-auth-library-nodejs 等开源库验证令牌。例如,您可以按以下方式验证 ID 令牌的 email

service-agent-project-number@gcp-sa-dialogflow.iam.gserviceaccount.com

示例

以下示例展示了如何接收 WebhookRequest 以及发送 WebhookResponse。这些示例引用了在快速入门中创建的意图。

Go

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
)

type intent struct {
	DisplayName string `json:"displayName"`
}

type queryResult struct {
	Intent intent `json:"intent"`
}

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"`
}

// welcome creates a response for the welcome intent.
func welcome(request webhookRequest) (webhookResponse, error) {
	response := webhookResponse{
		FulfillmentMessages: []message{
			{
				Text: text{
					Text: []string{"Welcome from Dialogflow Go Webhook"},
				},
			},
		},
	}
	return response, nil
}

// getAgentName creates a response for the get-agent-name intent.
func getAgentName(request webhookRequest) (webhookResponse, error) {
	response := webhookResponse{
		FulfillmentMessages: []message{
			{
				Text: text{
					Text: []string{"My name is Dialogflow Go Webhook"},
				},
			},
		},
	}
	return response, nil
}

// handleError handles internal errors.
func handleError(w http.ResponseWriter, err error) {
	w.WriteHeader(http.StatusInternalServerError)
	fmt.Fprintf(w, "ERROR: %v", err)
}

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

	// Call intent handler
	switch intent := request.QueryResult.Intent.DisplayName; intent {
	case "Default Welcome Intent":
		response, err = welcome(request)
	case "get-agent-name":
		response, err = getAgentName(request)
	default:
		err = fmt.Errorf("Unknown intent: %s", intent)
	}
	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
	}
}

Java


// TODO: add GSON dependency to Pom file
// (https://mvnrepository.com/artifact/com.google.code.gson/gson/2.8.5)
// TODO: Uncomment the line bellow before running cloud function
// package com.example;

import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.BufferedWriter;

public class Example implements HttpFunction {

  public void service(HttpRequest request, HttpResponse response) throws Exception {
    JsonParser parser = new JsonParser();
    Gson gson = new GsonBuilder().create();

    JsonObject job = gson.fromJson(request.getReader(), JsonObject.class);
    String str =
        job.getAsJsonObject("queryResult")
            .getAsJsonObject("intent")
            .getAsJsonPrimitive("displayName")
            .toString();
    JsonObject o = null;
    String a = '"' + "Default Welcome Intent" + '"';
    String b = '"' + "get-agent-name" + '"';
    String responseText = "";

    if (str.equals(a)) {
      responseText = '"' + "Hello from a Java GCF Webhook" + '"';
    } else if (str.equals(b)) {
      responseText = '"' + "My name is Flowhook" + '"';
    } else {
      responseText = '"' + "Sorry I didn't get that" + '"';
    }

    o =
        parser
            .parse(
                "{\"fulfillmentMessages\": [ { \"text\": { \"text\": [ "
                    + responseText
                    + " ] } } ] }")
            .getAsJsonObject();

    BufferedWriter writer = response.getWriter();
    writer.write(o.toString());
  }
}

Node.js


// TODO: Add handleWebhook to 'Entry point' in the Google Cloud Function
exports.handleWebhook = (request, response) => {
  const tag = request.body.queryResult.intent.displayName;

  let jsonResponse = {};
  if (tag === 'Default Welcome Intent') {
    //fulfillment response to be sent to the agent if the request tag is equal to "welcome tag"
    jsonResponse = {
      fulfillment_messages: [
        {
          text: {
            //fulfillment text response to be sent to the agent
            text: ['Hello from a GCF Webhook'],
          },
        },
      ],
    };
  } else if (tag === 'get-name') {
    //fulfillment response to be sent to the agent if the request tag is equal to "welcome tag"
    jsonResponse = {
      fulfillment_messages: [
        {
          text: {
            //fulfillment text response to be sent to the agent
            text: ['My name is Flowhook'],
          },
        },
      ],
    };
  } else {
    jsonResponse = {
      //fulfillment text response to be sent to the agent if there are no defined responses for the specified tag
      fulfillment_messages: [
        {
          text: {
            ////fulfillment text response to be sent to the agent
            text: [
              `There are no fulfillment responses defined for "${tag}"" tag`,
            ],
          },
        },
      ],
    };
  }
  response.send(jsonResponse);
};

Python


# TODO: change the default Entry Point text to handleWebhook

def handleWebhook(request):

    req = request.get_json()

    responseText = ""
    intent = req["queryResult"]["intent"]["displayName"]

    if intent == "Default Welcome Intent":
        responseText = "Hello from a GCF Webhook"
    elif intent == "get-agent-name":
        responseText = "My name is Flowhook"
    else:
        responseText = f"There are no fulfillment responses defined for Intent {intent}"

    # You can also use the google.cloud.dialogflowcx_v3.types.WebhookRequest protos instead of manually writing the json object
    res = {"fulfillmentMessages": [{"text": {"text": [responseText]}}]}

    return res