Мы реализуем Действия в Google с выполнением Dialogflow, используя недавно выпущенный Java / Kotlin API .
Это называется Speech Bank
.
Во время тестирования процесса Linking Link на смартфоне пользователь получает ошибку 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шаги:
хиты /login
конечная точка для предоставления учетных данных
хиты /token
конечная точка для получения токена (его значение token1
в журналах ниже. У нас есть возможность генерировать и вводить наши собственные токены, это тестирование enviroТаким образом, мы создали это значение token1
, которое, похоже, было успешно включено в последующий запрос.)
Ниже приведен подробный снимок экрана неудачного взаимодействия с приложенным журналом, предоставленнымДействия в консоли 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 исключение и что нужно изменить, чтобы устранить его.
Это исключение настолько неясно, что вызывает множество вопросов и заставляет задуматься о том, с чего начать.Я перечислю только несколько здесь и буду очень признателен за некоторые рекомендации.
Должна ли быть какая-либо корреляция между именами намерений в AoG и Dialogflow?Должны ли они следовать любому соглашению об именах?Может ли причина ошибки заключаться в том, что их как-то неправильно назвали?
Может ли MalformedResponse интерпретироваться как отсутствие определенного поля в ответе?Так как Google решил раскрыть внутреннюю работу преобразования между различными форматами сообщений (Dialogflow и AppResponse), есть ли где-нибудь список каких полей в ответе Dialogflow?
Это означает, что даже сообщения OAuth, которые передаются в этом случае, должны содержать некоторую речь?
Первоначально userId
, полученный из Dialogflow, кажется, всегда равен null
, но текст запроса, похоже, заполнен GOOGLE_ASSISTANT_WELCOME
, поэтому мы запускаем логику потока привязки аккаунтаисходя из предположения, что это null
.Это правильное предположение, чтобы иметь?
При каких обстоятельствах будет первоначально заполняться userId
(как в Alexa, где он автоматически генерируется после включения навыка для пользователя), так что else
может ли сработать указанное выше условие?
Должен ли токен OAuth, выдаваемый инфраструктурой аутентификации и поддерживаемый AoG, иметь какой-либо конкретный формат, например OIDC или JWT.Это может быть какая-нибудь случайная строка?Является ли token1
по-прежнему действительным токеном на языке AoG (как в Alexa)?
Любой неправильно сконфигурированный обработчик (и) Java-намерений?На какое имя намерения в ответе из потока ссылок аккаунта AoG мы должны реагировать?
Существуют ли имена для всех целей, обработчик которых может быть включен в приложение Java для облегчения дальнейшей отладки вышеуказанного?
Что подразумевается под «пустым речевым ответом», какие значения мы не предоставляем, которые ожидаются и вызывают поломку?
Все, что мы настроили, что не должнобыли настроены?
Если это вообще имеет значение, вот журнал из нашего 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] }