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

自作多情 提交于 2019-12-11 00:05:47

问题


We are implementing Actions on Google with Dialogflow fulfillment using the newly released Java/Kotlin API.

It's called Speech Bank.

While going through the Account Linking process testing on the smartphone, the user is getting MalformedResponse error preventing the completion of the flow and consequent successful hand-off back to the regular flow.

The logs (detailed below) contain the MalformedResponse: Failed to parse Dialogflow response into AppResponse because of empty speech response message, and the user receives Speech bank isn't responding right now. Try again soon. message on her device.

Here's a bit more details on our setup:

The action is configured for Account Linking utilizing our own OAuth 2 compliant mock infrastructure.

There's a single intent (called RawText) configured in Dialogflow, the rest of the interactions are to be taken care of by own internal application via its web hook.

Here's how the state machine is coded in Java so far:

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

    }


}

As configured above, the OAuth authorization code flow undertakes the normal OAuth 2 steps:

  • hits /login endpoint to supply credentials

  • hits /token endpoint to obtain the token (its value is token1 in the below logs. We have a facility to generate & inject our own tokens, this is a testing environment so we produced this token1 value which seem to have been successfully incorporated into the subsequent request.)

Below is the detailed screen shot of failed interaction, with the attached log provided by Actions on Google console:

[
 {
   "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"
 }
]

Based on the above setup description, could anyone please give us a hand figuring out what's causing the MalformedResponse exception and what needs to change to eliminate it.

This exception is so obscure that it brings about a myriad questions and places one at one's wits end as to where to start approaching it. I'll list just a few here and would really appreciate some guidance.

  • Should there be any correlation between intent names in AoG and Dialogflow? Should they follow any naming convention? Could the cause of the error lie in their somehow being misnamed?

  • Can the MalformedResponse be interpreted as lack of a particular field on the response? Since Google chose to expose the inner workings of the conversion between different message formats (Dialogflow and AppResponse), is there a listing somewhere of what fields are required on a Dialogflow response?

  • Is this to imply that even the OAuth messages that are being passed around in this case need to contain some speech?

  • Initially, the userId received from Dialogflow seems to always be null but the query text seems to be populated with GOOGLE_ASSISTANT_WELCOME, so we are starting the account linking flow logic based on the assumption that it is null. Is that a right assumption to have?

  • Under which circumstances will the userId be initially populated (like in Alexa where it's autogenerated upon enabling a skill for the user) so that the else condition above could get triggered?

  • Should the OAuth token issued by the authentication infrastructure and supported by AoG be in any particular format, i.e. OIDC or JWT. Could it be any random string? Is token1 still a valid token in AoG parlance (as it is in Alexa)?

  • Any misconfigured Java intent handler(s)? Which intent name in the response from the AoG account linking flow should we be reacting to?

  • Is there a catch-all intent name(s), a handler for which can be incorporated into the Java app to facilitate further debugging of the above?

  • What is meant by "empty speech response", what values are we not providing that are expected and cause the breakage?

  • Anything that we have configured which should not have been configured?

If it matters at all, here's the log from our 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] }

回答1:


this line

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

Will create your response with SIGN_IN event. So in you dialogFlow you need to add another intent with actions_intent_SIGN_IN, and in your Java you need to implement it also, here you can find more info.

example dialogFlow:



来源:https://stackoverflow.com/questions/54810597/malformedresponse-failed-to-parse-dialogflow-response-into-appresponse-because

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!