"MalformedResponse: Failed to parse Dialogflow response into AppResponse because of empty speech response" 在 OAuth 帐户链接流程中

"MalformedResponse: Failed to parse Dialogflow response into AppResponse because of empty speech response" during OAuth Account Linking flow

我们正在 Google 上使用新发布的 Java/Kotlin API 通过 Dialogflow fulfillment 执行操作。

叫做Speech Bank

在智能手机上进行 帐户链接 流程测试时,用户收到 MalformedResponse 错误,阻止流程完成并随后成功切换回常规流程.

日志(详见下文)包含 MalformedResponse: Failed to parse Dialogflow response into AppResponse because of empty speech response 消息,用户在她的设备上收到 Speech bank isn't responding right now. Try again soon. 消息。

这里有一些关于我们设置的更多细节:

该操作已配置为使用我们自己的 OAuth 2 兼容模拟基础设施的帐户链接。

在 Dialogflow 中配置了一个意图(称为 RawText),其余的交互将由自己的内部应用程序通过其网络挂钩处理。

到目前为止,状态机在 Java 中的编码方式如下:

    public class AoGApp extends DialogflowApp {

        private final static Logger log = LoggerFactory.getLogger(AoGApp.class);

       public static final String GREETING = "GOOGLE_ASSISTANT_WELCOME";

        @ForIntent("RawText")
        //@ForIntent("actions.intent.MAIN")
        public ActionResponse launchRequestHandler(ActionRequest request) {

            String userId = request.getAppRequest().getUser().getUserId();
            log.info("userId={}",userId);
            String queryText = request.getWebhookRequest().getQueryResult().getQueryText();
            log.info("queryText={}", queryText);


            String speech = null;

            ResponseBuilder responseBuilder = getResponseBuilder(request);

            if (isBlank(userId) || GREETING.equalsIgnoreCase(queryText)) {

                speech = "\nHi. I sense a great banking experience in your future, I see that your account isn't connected. "
                        + "I've sent a link to your Google Assistant app that will get you started and set up in just several simple steps. "
                        + "Don't worry, I'll be here waiting, just summon me when you're ready.";


                responseBuilder.add(
                        new SignIn()
                            .setContext(speech));
            } else {
                speech = "Welcome. You can say hello.";
                responseBuilder.add(speech);
            }


            return responseBuilder.build();
        }

        @ForIntent("actions.intent.SIGN_IN")
        public ActionResponse getSignInStatus(ActionRequest request) {
          ResponseBuilder responseBuilder = getResponseBuilder(request);
          String text = "Hello from sign-in handler";
          responseBuilder.add(text);
          log.info(text);
          return responseBuilder.build();

        }

    }


and the associated HttpRequest processing: 

    @Override
        protected void handlePOST(final Request request, final HttpServletResponse response) {

            try {
                String rawRequest = ControllerUtils.toString(request.getReader());


                String jsonResponse = app.handleRequest(rawRequest, getHeadersMap(request)).get();
                log.info("Generated response:\n {}", ControllerUtils.prettyPrint(jsonResponse));
                response.setContentType(APPLICATION_JSON.getMimeType());
                response.getWriter().write(jsonResponse);
            } catch (Exception e) {
                handleError(response, e);
            } 
        }

public final class ControllerUtils {

    private final static Logger log = LoggerFactory.getLogger(ControllerUtils.class);
    private static ObjectMapper mapper = new ObjectMapper();

    public static String toString(BufferedReader reader) throws Exception {

        String rawRequest = reader
                .lines()
                //.map(e -> e.concat(System.lineSeparator()))
                .collect(Collectors.joining(System.lineSeparator()));
        log.info("Received AoG Request {}",rawRequest);
        return rawRequest;
    }

    public static String prettyPrint(String json) throws Exception {
        return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mapper.readValue(json, Object.class));
    }

    public static Map<String, String> getHeadersMap(org.eclipse.jetty.server.Request jettyRequest){
        return Collections.list((Enumeration<String>) jettyRequest.getHeaderNames())
                .stream()
                .collect(Collectors.toMap(
                        name -> name,
                        jettyRequest::getHeader));

    }


}

如上配置,OAuth授权代码流程承担正常的OAuth 2步骤:

以下是失败交互的详细屏幕截图,附有 Actions 在 Google 控制台上提供的日志:

[
 {
   "textPayload": "Sending request with post data: {\"user\":{\"userId\":\"ABwppHFQHUBr0RrWA_OuL-kK2sxTPUvQtL3D-x2Ydr-7uxLt9zzEFzJrGB-X96d9XY8k9XTJj-RUg9WpzGB9jg\",\"locale\":\"en-US\",\"lastSeen\":\"2019-02-20T21:32:22Z\",\"userStorage\":\"{\\"data\\":{}}\"},\"conversation\":{\"conversationId\":\"ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug\",\"type\":\"NEW\"},\"inputs\":[{\"intent\":\"actions.intent.MAIN\",\"rawInputs\":[{\"inputType\":\"VOICE\",\"query\":\"open speech Bank\"}]}],\"surface\":{\"capabilities\":[{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.MEDIA_RESPONSE_AUDIO\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.WEB_BROWSER\"}]},\"isInSandbox\":true,\"availableSurfaces\":[{\"capabilities\":[{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.WEB_BROWSER\"}]}]}.",
   "insertId": "f9fzrtf3hjgn4",
   "resource": {
     "type": "assistant_action",
     "labels": {
       "project_id": "speechbank-e8a15",
       "version_id": "",
       "action_id": "actions.intent.MAIN"
     }
   },
   "timestamp": "2019-02-21T13:47:56.713587946Z",
   "severity": "DEBUG",
   "labels": {
     "channel": "preview",
     "source": "AOG_REQUEST_RESPONSE",
     "querystream": "GOOGLE_USER"
   },
   "logName": "projects/speechbank-e8a15/logs/actions.googleapis.com%2Factions",
   "trace": "projects/366800784520/traces/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug",
   "receiveTimestamp": "2019-02-21T13:47:57.205496026Z"
 },
 {
   "textPayload": "Received response from agent with body: HTTP/1.1 200 OK\r\nServer: nginx/1.13.6\r\nDate: Thu, 21 Feb 2019 13:47:57 GMT\r\nContent-Type: application/json;charset=UTF-8\r\nContent-Length: 426\r\nX-Cloud-Trace-Context: d8cb97627afa1d2977b9f567f29598de/11157405402824233090;o=0\r\nGoogle-Actions-API-Version: 2\r\nX-SHARD: shard-2\r\nVia: 1.1 google\r\nAlt-Svc: clear\r\n\r\n{\"conversationToken\":\"[\\"_actions_on_google\\"]\",\"expectUserResponse\":true,\"expectedInputs\":[{\"inputPrompt\":{},\"possibleIntents\":[{\"intent\":\"actions.intent.SIGN_IN\",\"inputValueData\":{\"@type\":\"type.googleapis.com/google.actions.v2.SignInValueSpec\"}}]}],\"responseMetadata\":{\"status\":{\"message\":\"Success (200)\"},\"queryMatchInfo\":{\"queryMatched\":true,\"intent\":\"f645f492-f6dc-4e7e-8da6-45711c654ad0\"}},\"userStorage\":\"{\\"data\\":{}}\"}.",
   "insertId": "f9fzrtf3hjgn5",
   "resource": {
     "type": "assistant_action",
     "labels": {
       "version_id": "",
       "action_id": "actions.intent.MAIN",
       "project_id": "speechbank-e8a15"
     }
   },
   "timestamp": "2019-02-21T13:47:57.190979036Z",
   "severity": "DEBUG",
   "labels": {
     "source": "AOG_REQUEST_RESPONSE",
     "querystream": "GOOGLE_USER",
     "channel": "preview"
   },
   "logName": "projects/speechbank-e8a15/logs/actions.googleapis.com%2Factions",
   "trace": "projects/366800784520/traces/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug",
   "receiveTimestamp": "2019-02-21T13:47:57.205496026Z"
 },
 {
   "textPayload": "Sending request with post data: {\"user\":{\"userId\":\"ABwppHFQHUBr0RrWA_OuL-kK2sxTPUvQtL3D-x2Ydr-7uxLt9zzEFzJrGB-X96d9XY8k9XTJj-RUg9WpzGB9jg\",\"accessToken\":\"token1\",\"locale\":\"en-US\",\"lastSeen\":\"2019-02-20T21:32:22Z\",\"userStorage\":\"{\\"data\\":{}}\"},\"conversation\":{\"conversationId\":\"ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug\",\"type\":\"ACTIVE\",\"conversationToken\":\"[\\"_actions_on_google\\"]\"},\"inputs\":[{\"intent\":\"actions.intent.SIGN_IN\",\"rawInputs\":[{}],\"arguments\":[{\"name\":\"SIGN_IN\",\"extension\":{\"@type\":\"type.googleapis.com/google.actions.v2.SignInValue\",\"status\":\"OK\"}},{\"name\":\"text\"}]}],\"surface\":{\"capabilities\":[{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.MEDIA_RESPONSE_AUDIO\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"}]},\"isInSandbox\":true,\"availableSurfaces\":[{\"capabilities\":[{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"}]}]}.",
   "insertId": "120k9w1f3jmw55",
   "resource": {
     "type": "assistant_action",
     "labels": {
       "version_id": "",
       "action_id": "actions.intent.SIGN_IN",
       "project_id": "speechbank-e8a15"
     }
   },
   "timestamp": "2019-02-21T13:48:28.768213970Z",
   "severity": "DEBUG",
   "labels": {
     "source": "AOG_REQUEST_RESPONSE",
     "querystream": "GOOGLE_USER",
     "channel": "preview"
   },
   "logName": "projects/speechbank-e8a15/logs/actions.googleapis.com%2Factions",
   "trace": "projects/366800784520/traces/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug",
   "receiveTimestamp": "2019-02-21T13:48:28.912828815Z"
 },
 {
   "textPayload": "Received response from agent with body: HTTP/1.1 200 OK\r\nServer: nginx/1.13.6\r\nDate: Thu, 21 Feb 2019 13:48:28 GMT\r\nContent-Type: application/json;charset=UTF-8\r\nContent-Length: 570\r\nX-Cloud-Trace-Context: 664d8fdaf9cd3d880d41f11ac2176e0e/16724608154084655134;o=0\r\nGoogle-Actions-API-Version: 2\r\nAssistant-Interaction-Error-Code: -1\r\nAssistant-Interaction-Error-Message: Failed to parse Dialogflow response into AppResponse because of empty speech response\r\nX-SHARD: shard-2\r\nVia: 1.1 google\r\nAlt-Svc: clear\r\n\r\n{\n  \"responseMetadata\": {\n    \"status\": {\n      \"code\": 10,\n      \"message\": \"Failed to parse Dialogflow response into AppResponse because of empty speech response\",\n      \"details\": [{\n        \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n        \"value\": \"{\\"id\\":\\"5d4bed8d-c58c-4429-9838-f758d6f335f2\\",\\"timestamp\\":\\"2019-02-21T13:48:28.806Z\\",\\"lang\\":\\"en-us\\",\\"result\\":{},\\"status\\":{\\"code\\":200,\\"errorType\\":\\"success\\"},\\"sessionId\\":\\"ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug\\"}\"\n      }]\n    }\n  }\n}.",
   "insertId": "120k9w1f3jmw56",
   "resource": {
     "type": "assistant_action",
     "labels": {
       "project_id": "speechbank-e8a15",
       "version_id": "",
       "action_id": "actions.intent.SIGN_IN"
     }
   },
   "timestamp": "2019-02-21T13:48:28.899033790Z",
   "severity": "DEBUG",
   "labels": {
     "channel": "preview",
     "source": "AOG_REQUEST_RESPONSE",
     "querystream": "GOOGLE_USER"
   },
   "logName": "projects/speechbank-e8a15/logs/actions.googleapis.com%2Factions",
   "trace": "projects/366800784520/traces/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug",
   "receiveTimestamp": "2019-02-21T13:48:28.912828815Z"
 },
 {
   "textPayload": "MalformedResponse: Failed to parse Dialogflow response into AppResponse because of empty speech response",
   "insertId": "1b6j2e6f39jvuy",
   "resource": {
     "type": "assistant_action",
     "labels": {
       "project_id": "speechbank-e8a15",
       "version_id": "",
       "action_id": "actions.intent.SIGN_IN"
     }
   },
   "timestamp": "2019-02-21T13:48:28.899403302Z",
   "severity": "ERROR",
   "labels": {
     "channel": "preview",
     "source": "JSON_RESPONSE_VALIDATION",
     "querystream": "GOOGLE_USER"
   },
   "logName": "projects/speechbank-e8a15/logs/actions.googleapis.com%2Factions",
   "trace": "projects/366800784520/traces/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug",
   "receiveTimestamp": "2019-02-21T13:48:28.914061262Z"
 }
]

根据上述设置说明,任何人都可以帮助我们找出导致 MalformedResponse 异常的原因以及需要更改哪些内容以消除它。

这个异常是如此晦涩难懂,以至于它带来了无数的问题,让人不知从哪里开始处理它。我将在这里仅列出一些,非常感谢您提供一些指导。

如果这很重要,这是来自我们的 webhook 的日志:

     [java] 02-21-2019 13:47:57 [qtp2056234595-127] INFO  domain.lola.user.utils.http.ControllerUtils [toString:30]    - Received AoG Request {
 [java]   "responseId": "0156911c-d7e8-405b-bf8f-f23320c02030",
 [java]   "queryResult": {
 [java]     "queryText": "GOOGLE_ASSISTANT_WELCOME",
 [java]     "parameters": {
 [java]       "any": ""
 [java]     },
 [java]     "allRequiredParamsPresent": true,
 [java]     "fulfillmentMessages": [{
 [java]       "text": {
 [java]         "text": [""]
 [java]       }
 [java]     }],
 [java]     "outputContexts": [{
 [java]       "name": "projects/speechbank-e8a15/agent/sessions/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug/contexts/google_assistant_welcome",
 [java]       "parameters": {
 [java]         "any.original": "",
 [java]         "any": ""
 [java]       }
 [java]     }, {
 [java]       "name": "projects/speechbank-e8a15/agent/sessions/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug/contexts/actions_capability_screen_output",
 [java]       "parameters": {
 [java]         "any.original": "",
 [java]         "any": ""
 [java]       }
 [java]     }, {
 [java]       "name": "projects/speechbank-e8a15/agent/sessions/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug/contexts/actions_capability_audio_output",
 [java]       "parameters": {
 [java]         "any.original": "",
 [java]         "any": ""
 [java]       }
 [java]     }, {
 [java]       "name": "projects/speechbank-e8a15/agent/sessions/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug/contexts/google_assistant_input_type_voice",
 [java]       "parameters": {
 [java]         "any.original": "",
 [java]         "any": ""
 [java]       }
 [java]     }, {
 [java]       "name": "projects/speechbank-e8a15/agent/sessions/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug/contexts/actions_capability_web_browser",
 [java]       "parameters": {
 [java]         "any.original": "",
 [java]         "any": ""
 [java]       }
 [java]     }, {
 [java]       "name": "projects/speechbank-e8a15/agent/sessions/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug/contexts/actions_capability_media_response_audio",
 [java]       "parameters": {
 [java]         "any.original": "",
 [java]         "any": ""
 [java]       }
 [java]     }],
 [java]     "intent": {
 [java]       "name": "projects/speechbank-e8a15/agent/intents/f645f492-f6dc-4e7e-8da6-45711c654ad0",
 [java]       "displayName": "RawText"
 [java]     },
 [java]     "intentDetectionConfidence": 1.0,
 [java]     "languageCode": "en-us"
 [java]   },
 [java]   "originalDetectIntentRequest": {
 [java]     "source": "google",
 [java]     "version": "2",
 [java]     "payload": {
 [java]       "isInSandbox": true,
 [java]       "surface": {
 [java]         "capabilities": [{
 [java]           "name": "actions.capability.AUDIO_OUTPUT"
 [java]         }, {
 [java]           "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
 [java]         }, {
 [java]           "name": "actions.capability.SCREEN_OUTPUT"
 [java]         }, {
 [java]           "name": "actions.capability.WEB_BROWSER"
 [java]         }]
 [java]       },
 [java]       "inputs": [{
 [java]         "rawInputs": [{
 [java]           "query": "open speech Bank",
 [java]           "inputType": "VOICE"
 [java]         }],
 [java]         "intent": "actions.intent.MAIN"
 [java]       }],
 [java]       "user": {
 [java]         "userStorage": "{\"data\":{}}",
 [java]         "lastSeen": "2019-02-20T21:32:22Z",
 [java]         "locale": "en-US",
 [java]         "userId": "ABwppHFQHUBr0RrWA_OuL-kK2sxTPUvQtL3D-x2Ydr-7uxLt9zzEFzJrGB-X96d9XY8k9XTJj-RUg9WpzGB9jg"
 [java]       },
 [java]       "conversation": {
 [java]         "conversationId": "ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug",
 [java]         "type": "NEW"
 [java]       },
 [java]       "availableSurfaces": [{
 [java]         "capabilities": [{
 [java]           "name": "actions.capability.AUDIO_OUTPUT"
 [java]         }, {
 [java]           "name": "actions.capability.SCREEN_OUTPUT"
 [java]         }, {
 [java]           "name": "actions.capability.WEB_BROWSER"
 [java]         }]
 [java]       }]
 [java]     }
 [java]   },
 [java]   "session": "projects/speechbank-e8a15/agent/sessions/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug"
 [java] }
 [java] 02-21-2019 13:47:57 [qtp2056234595-127] INFO  domain.lola.user.utils.actionsongoogle.AoGApp [launchRequestHandler:26]    - userId=null
 [java] 02-21-2019 13:47:57 [qtp2056234595-127] INFO  domain.lola.user.utils.actionsongoogle.AoGApp [launchRequestHandler:28]    - queryText=GOOGLE_ASSISTANT_WELCOME
 [java] 02-21-2019 13:47:57 [qtp2056234595-127] INFO  domain.lola.user.utils.actionsongoogle.AoGBotService [handlePOST:103]    - Generated response:
 [java]  {
 [java]   "outputContexts" : [ {
 [java]     "lifespanCount" : 99,
 [java]     "name" : "projects/speechbank-e8a15/agent/sessions/ABwppHE35s8T6qSdaaCMNiWuMdY7UsvQ3sHbLZJOQkVA4AFD2nhKuqTTvoJTkVh7yU81GCHbvlfZTxmULLd4Ug/contexts/_actions_on_google",
 [java]     "parameters" : {
 [java]       "data" : "{}"
 [java]     }
 [java]   } ],
 [java]   "payload" : {
 [java]     "google" : {
 [java]       "expectUserResponse" : true,
 [java]       "isSsml" : false,
 [java]       "systemIntent" : {
 [java]         "intent" : "actions.intent.SIGN_IN",
 [java]         "data" : {
 [java]           "@type" : "type.googleapis.com/google.actions.v2.SignInValueSpec"
 [java]         }
 [java]       },
 [java]       "userStorage" : "{\"data\":{}}"
 [java]     }
 [java]   }
 [java] }

这一行

responseBuilder.add(new SignIn().setContext(speech));

将创建您对 SIGN_IN 事件的响应。 因此,在您的 dialogFlow 中,您需要使用 actions_intent_SIGN_IN 添加另一个意图, 在你的 Java 中你也需要实现它,here 你可以找到更多信息。

示例对话流: