Storing data “across conversations” in Google Action

烂漫一生 提交于 2020-01-03 18:34:10

问题


I'm a bit confused by the Google Actions documentation about storing data and hoped someone can help clarify...

The docs state that data in the conv.user.storage object will be saved "across conversations". I took this to mean that if the user exited the conversation these values would be persisted and available the next time they interact with my action. Is that understanding correct?

The reason I ask is that I can't get this behaviour to work in my action.

I have built a simple action fulfilment service (using Actions on Google NodeJS library v2.4.0 and Koa v2.5.3). The fulfilment is triggered from an intent defined in Dialogflow (after an account has been linked with Google Sign In) and stores a value in conversation storage. The code is as follows:

server.js (base server - loads actions dynamically from the local ./actions/ dir)

/* Load the environment */
const dotenv = require('dotenv');
const path = require('path');
const packageJson = require('./package.json');
dotenv.config({
    silent: true,
    path: process.env.ENV_FILE!=undefined && process.env.ENV_FILE.trim()!='' ? path.normalize(process.env.ENV_FILE) : path.join(__dirname, './.env')
});

const SERVER_NAME = process.env.NAME || packageJson.name;
const SERVER_PORT = process.env.PORT||'8080';
const SERVER_HOST = process.env.HOST||'0.0.0.0';
const HANDLERS_PATH = './actions/';


/* Load the dependencies */
const logger = require('utils-general').logger('google-server');
const Koa = require('koa');
const KoaBody = require('koa-body');
const KoaActionsOnGoogle = require('koa-aog');
const fs = require('fs');
const { dialogflow } = require('actions-on-google');


/* Load and initialise the Google Assistant actions */
//Initialise DialogFlow
const action = dialogflow({ debug: process.env.ACTIONS_DEBUG==='true', clientId: process.env.GOOGLE_CLIENT_ID });

//Load the action intent handlers
const handlers = [];
let handlerFiles = fs.readdirSync(HANDLERS_PATH);
handlerFiles.forEach(function loadHandlers(file) {
    let handlerImpl = require(HANDLERS_PATH+file);
    let handler = {};
    handler[handlerImpl.intent] = handlerImpl.action;
    handlers.push(handler);
});

//Add the actions intent handlers to DialogFlow
handlers.forEach(item => {
    let key = Object.keys(item)[0];
    logger.info(`Adding handler for action intent ${key}`);
    action.intent(key, item[key]);
});


/* Create the application server to handle fulfilment requests */
logger.info(`Initialising the ${SERVER_NAME} server (port: ${SERVER_PORT}, host: ${SERVER_HOST})`);

//Create the server
const app = new Koa();

//Add default error handler middleware
app.on('error', function handleAppError(err) {
    logger.error(`Unhandled ${err.name||'Error'}: ${err.message || JSON.stringify(err)}`);
});

//Add body parsing middleware
app.use(KoaBody({ jsonLimit: '50kb' }));

//Log the request/ response
app.use(async (ctx, next) => {
    logger.trace(`REQUEST ${ctx.method} ${ctx.path} ${JSON.stringify(ctx.request.body)}`);
    await next();
    logger.trace(`RESPONSE (${ctx.response.status}) ${ctx.response.body ? JSON.stringify(ctx.response.body) : ''}`);
});

//Make the action fulfilment endpoint available on the server
app.use(KoaActionsOnGoogle({ action: action }));


/* Start server on the specified port */
app.listen(SERVER_PORT, SERVER_HOST, function () {
    logger.info(`${SERVER_NAME} server started at ${new Date().toISOString()} and listening for requests on port ${SERVER_PORT}`);
});

module.exports = app;

storage-read.js (fulfilment for the "STORAGE_READ" intent - reads stored uuid from conversation storage):

const logger = require('utils-general').logger('google-action-storage-read');
const { SimpleResponse } = require('actions-on-google');
const { getUserId } = require('../utils/assistant-util');
const _get = require('lodash.get');

module.exports = {
    intent: 'STORAGE_READ',
    action: async function (conv, input) {
        logger.debug(`Processing STORAGE_READ intent request: ${JSON.stringify(conv)}`, { traceid: getUserId(conv) });
        let storedId = _get(conv, 'user.storage.uuid', undefined);
        logger.debug(`User storage UUID is ${storedId}`);
        conv.close(new SimpleResponse((storedId!=undefined ? `This conversation contains stored data` : `There is no stored data for this conversation`)));
    }
}

storage-write.js (fulfils the "STORAGE_WRITE" intent - writes a UUID to conversation storage):

const logger = require('utils-general').logger('google-action-storage-read');
const { SimpleResponse } = require('actions-on-google');
const { getUserId } = require('../utils/assistant-util');
const _set = require('lodash.set');
const uuid = require('uuid/v4');

module.exports = {
    intent: 'STORAGE_WRITE',
    action: async function (conv, input) {
        logger.debug(`Processing STORAGE_WRITE intent request`, { traceid: getUserId(conv) });
        let newId = uuid();
        logger.debug(`Writing new UUID to conversation storage: ${newId}`);
        _set(conv, 'user.storage.uuid', newId);
        conv.close(new SimpleResponse(`OK, I've written a new UUID to conversation storage`));
    }
}

This "STORAGE_WRITE" fulfilment stores the data and makes it available between turns in the same conversation (i.e. another intent triggered in the same conversation can read the stored data). However, when the conversation is closed, subsequent (new) conversations with the same user are unable to read the data (i.e. when the "STORAGE_READ" intent is fulfilled) - the conv.user.storage object is always empty.

I have voice match set up on the Google account/ Home Mini I'm using, but I can't see how I determine in the action if the voice is matched (although it seems to be as when I start a new conversation my linked account is used). I'm also getting the same behaviour on the simulator.

Sample request/ responses (when using the simulator) are as follows:

STORAGE_WRITE request:

{
  "user": {
    "userId": "AB_Hidden_EWVzx3q",
    "locale": "en-US",
    "lastSeen": "2018-10-18T12:52:01Z",
    "idToken": "eyMyHiddenTokenId"
  },
  "conversation": {
    "conversationId": "ABwppHFrP5DIKzykGIfK5mNS42yVzuunzOfFUhyPctG0h0xM8p6u0E9suX8OIvaaGdlYydTl60ih-WJ5kkqV4acS5Zd1OkRJ5pnE",
    "type": "NEW"
  },
  "inputs": [
    {
      "intent": "actions.intent.MAIN",
      "rawInputs": [
        {
          "inputType": "KEYBOARD",
          "query": "ask my pathfinder to write something to conversation storage"
        }
      ],
      "arguments": [
        {
          "name": "trigger_query",
          "rawText": "write something to conversation storage",
          "textValue": "write something to conversation storage"
        }
      ]
    }
  ],
  "surface": {
    "capabilities": [
      {
        "name": "actions.capability.WEB_BROWSER"
      },
      {
        "name": "actions.capability.AUDIO_OUTPUT"
      },
      {
        "name": "actions.capability.SCREEN_OUTPUT"
      },
      {
        "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
      }
    ]
  },
  "isInSandbox": true,
  "availableSurfaces": [
    {
      "capabilities": [
        {
          "name": "actions.capability.WEB_BROWSER"
        },
        {
          "name": "actions.capability.AUDIO_OUTPUT"
        },
        {
          "name": "actions.capability.SCREEN_OUTPUT"
        }
      ]
    }
  ],
  "requestType": "SIMULATOR"
}

STORAGE_WRITE response:

{
  "conversationToken": "[]",
  "finalResponse": {
    "richResponse": {
      "items": [
        {
          "simpleResponse": {
            "textToSpeech": "OK, I've written a new UUID to conversation storage"
          }
        }
      ]
    }
  },
  "responseMetadata": {
    "status": {
      "message": "Success (200)"
    },
    "queryMatchInfo": {
      "queryMatched": true,
      "intent": "a7e54fcf-8ff1-4690-a311-e4c6a8d1bfd7"
    }
  },
  "userStorage": "{\"data\":{\"uuid\":\"7dc835fa-0470-4028-b8ed-3374ed65ac7c\"}}"
}

Subsequent STORAGE_READ request:

{
    "user": {
        "userId": "AB_Hidden_EWVzx3q",
        "locale": "en-US",
        "lastSeen": "2018-10-18T12:52:47Z",
        "idToken": "eyMyHiddenTokenId"
    },
    "conversation": {
        "conversationId": "ABwppHHVvp810VEfa4BhBJPf1NIfKUGzyvw9JCw7kKq9YBd_F8w0VYjJiSuzGLrHcXHGc9pC6ukuMB62XVkzkZOaC24pEbXWLQX5",
        "type": "NEW"
    },
    "inputs": [
        {
            "intent": "STORAGE_READ",
            "rawInputs": [
                {
                    "inputType": "KEYBOARD",
                    "query": "ask my pathfinder what is in conversation storage"
                }
            ],
            "arguments": [
                {
                    "name": "trigger_query",
                    "rawText": "what is in conversation storage",
                    "textValue": "what is in conversation storage"
                }
            ]
        }
    ],
    "surface": {
        "capabilities": [
            {
                "name": "actions.capability.WEB_BROWSER"
            },
            {
                "name": "actions.capability.AUDIO_OUTPUT"
            },
            {
                "name": "actions.capability.SCREEN_OUTPUT"
            },
            {
                "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
            }
        ]
    },
    "isInSandbox": true,
    "availableSurfaces": [
        {
            "capabilities": [
                {
                    "name": "actions.capability.WEB_BROWSER"
                },
                {
                    "name": "actions.capability.AUDIO_OUTPUT"
                },
                {
                    "name": "actions.capability.SCREEN_OUTPUT"
                }
            ]
        }
    ],
    "requestType": "SIMULATOR"
}

STORAGE_READ response:

    {
        "conversationToken": "[]",
        "finalResponse": {
            "richResponse": {
                "items": [
                    {
                        "simpleResponse": {
                            "textToSpeech": "There is no stored data for this conversation"
                        }
                    }
                ]
            }
        },
        "responseMetadata": {
            "status": {
                "message": "Success (200)"
            },
            "queryMatchInfo": {
                "queryMatched": true,
                "intent": "368d08d3-fe0c-4481-aa8e-b0bdfa659eeb"
            }
        }
    }

Can someone set me straighten me out on whether I'm misinterpreting the docs or maybe I have a bug somewhere?

Thanks!


回答1:


my suspicion is that personal results are turned off in your case.

You mentioned you're testing on Home Mini and Prisoner was able reproduce on device (in the comments).

Shared devices like Smart Speakers (Home, Mini) and Smart Displays have personal results disabled by default. Check this documentation to enable it.

  1. Open Settings on your Android phone
  2. Under "Assistant devices," select your device (e.g. Mini)
  3. Turn Personal results on

Beware that this means personal results like Calendar entries can be accessed through the device.

To check if userStorage will persist, you can use the GUEST/VERIFIED flag, see documentation here.



来源:https://stackoverflow.com/questions/52779281/storing-data-across-conversations-in-google-action

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