Arthur Frankel
Arthur Frankel

Reputation: 4705

Maintaining state within a conversation within DialogFlow

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.

Upvotes: 1

Views: 250

Answers (1)

Prisoner
Prisoner

Reputation: 50711

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.

Upvotes: 2

Related Questions