1

I am trying to understand how I can manage some aspect of state within fullfillment (DialogFlow's implementation where you can write code in JavaScript and it executes within a Google Cloud Function). First, I would assume that this implementation is stateless, but there must be a way to maintain some state without having to store the data in the database and then retrieve it on the next execution.

I would simply like to maintain the full history of the chat - the question asked by the user, and the response from the chatbot. I can see that I can get this information on every response (and call to the fullfillment) via:

  console.log(JSON.stringify(request.body.queryResult.queryText));
  console.log(JSON.stringify(request.body.queryResult.fulfillmentText)); 

Now that I have this information I just want to append it to some variable that is statefull. I have looked into setContext, context.set, app.data, and other functions/variables, but I can't seem to get it working because I'm not sure I understand how it should work.

In my code I have mostly the basic template. I don't think I can use a global variable so how can I store this state (fullConversation) between intent executions just for this user's conversation?

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });
  console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
  console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
  let query = JSON.stringify(request.body.queryResult.queryText);
  let response = console.log(JSON.stringify(request.body.queryResult.fulfillmentText); 

  // here I want to retrieve the prior query/response and append it
  // i.e., let fullConversation = fullConversation + query + response
  }
  
  function welcome(agent) {
    agent.add(`Welcome to my agent!`);
  }
 
  function fallback(agent) {
    agent.add(`I didn't understand`);
    agent.add(`I'm sorry, can you try again?`);
  }

  function myNewHandler(agent) {
  }

  // Run the proper function handler based on the matched Dialogflow intent name
  let intentMap = new Map();
  intentMap.set('Default Welcome Intent', welcome);
  intentMap.set('Default Fallback Intent', fallback);
  intentMap.set('myIntent',myNewHandler);
  agent.handleRequest(intentMap);
});

UPDATE: If I update my code with the code suggestion from @Prisoner I'm still having issues with just getting the context. I never get to my console.log(2). Do I need to move the agent.context.get code outside of the onRequest block??:

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });
  console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
  console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
  console.log(JSON.stringify(request.body.queryResult.queryText));
  console.log(JSON.stringify(request.body.queryResult.fulfillmentText)); 

  console.log("1");
  // Get what was set before, or an empty array
  const historyContext = agent.context.get('history');
  console.log("2");

SECOND UPDATE: The issue is related to a known issue solved here.

Just needed to update the dialogflow-fulfillment in package.json and everything worked.

Arthur Frankel
  • 4,695
  • 6
  • 35
  • 56

1 Answers1

2

You're on the right track. Global variables are definitely not the way to do it. And state can be maintained as part of a Context.

The app.data property is only available if you're using the actions-on-google library, which it does not look like you're using. Several of the APIs have also changed over time, and can be confusing. See this older answer for an examination of some of the options.

Since you're using the dialogflow-fulfillment library, you'll be using the agent.context (note the singular) object to add new contexts. For the context, you'll want to set a context parameter with the value that you want to store. Values need to be strings - so if you have something like an array, you probably want to convert it to a string using something like JSON.serialzie() and extract it with JSON.parse().

The code that gets the current context with your stored information, and then updates it with the latest values, might look something like this:

  // Get what was set before, or an empty array
  const historyContext = agent.context.get('history');
  const historyString = (historyContext && historyContext.params && historyContext.params.history) || '[]';
   const history = JSON.parse(historyString);

   // Add the messages as a single object
   history.push({
     requestMessage,
     responseMessage
   });

   // Save this as a context with a long lifespan
   agent.context.set('history', 99, JSON.stringify(history));

Update

I would put this code in a function, and call this function before you return from each handler function you're in. I'm a little surprised that agent.context would be causing problems outside the handler - but since you don't seem to have any specific error, that's my best guess.

Prisoner
  • 49,922
  • 7
  • 53
  • 105
  • Perfect! Now, when/why would I want to use the actions-on-google library? I didn't know about that library. – Arthur Frankel Aug 26 '20 at 17:55
  • 1
    That might be best asked as another question (feel free to ask it!) but in short - if you're *only* building an Action. – Prisoner Aug 26 '20 at 18:01
  • I'm in the onRequest function and this initial statement fails: const historyContext = agent.context.get('history'); It's hard to decipher why with the google cloud logs. It just says "crash" - not sure how I can see a code stack trace. The agent is defined as: const agent = new WebhookClient({ request, response }); – Arthur Frankel Aug 26 '20 at 18:40
  • The full error log should be available through the Stackdriver page in console.cloud.google.com, or from the Google Cloud Function page. You may need to filter on error level responses. Adding the full error to the question (or a new question) will help. If it is just failing somewhere in the function, its a little difficult to diagnose where unless we see how the function currently looks. – Prisoner Aug 26 '20 at 18:51
  • 1
    Please don't try to put code into comments - either update the question or ask a new question. – Prisoner Aug 26 '20 at 19:21
  • Difficult to diagnose what's going on since we don't see an actual error, but I've updated my answer a bit to suggest where I would put the code. – Prisoner Aug 27 '20 at 11:28
  • 1
    Thanks, I did a second update which seemed to work. For some reason, I needed to update the package version from what was in the original template. – Arthur Frankel Aug 27 '20 at 18:38