Search code examples
node.jsgoogle-cloud-functionsdialogflow-esopensearch

OpenSearch call in Dialogflow webhook intent response does not return data


I'm working on an app to retrieve data from a library catalog from a Dialogflow chat. I'm not getting any errors, and I have a billing account attached to the service. The intent code is here:

const {WebhookClient} = require('dialogflow-fulfillment');
const function = require('firebase-functions');
const agent = new WebhookClient({ request: request, response: response });
const catalogSearch = require("rss-to-json");  

exports.libraryChat = functions.https.onRequest(request, response) => {  

    function catalog_search(agent) {
        var itemSubject = agent.parameters["item-subject"] ? "+" + agent.parameters["item-subject"] : "";
        var itemTitle = agent.parameters["item-title"] ? "+" + agent.parameters["item-title"] : "";
        var chatResponse = "";
        var itemList = new Array();
        if (agent.parameters["author-name"]) {
            var authorName = agent.parameters["author-name"]["name"] ? "+" + agent.parameters["author-name"]["name"] + " " + agent.parameters["author-name"]["last-name"] : "+" + agent.parameters["author-name"]["last-name"];
        }
        var searchString = "";
        if (itemSubject.length > 0) { searchString = searchString  + itemSubject; }
        if (itemTitle.length > 0 ) { searchString = searchString + itemTitle; }
        if (authorName.length > 0) { searchString = searchString + authorName; }
        var url = "https://gapines.org/opac/extras/opensearch/1.1/-/rss2-full?searchTerms=site(GCHR-CCO)" + searchString + "&searchClass=keyword";
        console.log(url);
        catalogSearch.load(url, (err, jsonResponse) => {
            if (!err) {
                itemList = jsonResponse.items;
                chatResponse = "The first ten items returned for your search are: ";
            }
            else {
                chatResponse = "I'm sorry! I've encountered an error while retrieving that data!";
            }
        });
        itemList.forEach( (title, index) => {
            chatResponse = chatResponse + (index + 1).toString() + title.title;
        });
        agent.add(chatResponse);
    }

    let intentMap = new Map();
    intentMap.set("Catalog Search", catalog_search);
}

The JSON response from the intent is:

{
  "responseId": "958f0d66-13ba-4bf5-bed8-83480da4c37e",
  "queryResult": {
    "queryText": "Do you have any books about goats?",
    "parameters": {
      "item-subject": "goats",
      "item-title": "",
      "author-name": ""
    },
    "allRequiredParamsPresent": true,
    "fulfillmentMessages": [
      {
        "text": {
          "text": [
            ""
          ]
        }
      }
    ],
    "intent": {
      "name": "projects/library-chatbot/agent/intents/a5f8ad9b-ff73-49f7-a8c0-351da3bf4802",
      "displayName": "Catalog Search"
    },
    "intentDetectionConfidence": 1,
    "diagnosticInfo": {
      "webhook_latency_ms": 3591
    },
    "languageCode": "en"
  },
  "webhookStatus": {
    "message": "Webhook execution successful"
  }
}

I've verified through a separate function that the Opensearch call works and produces a list of titles, however when accessed through the Dialogflow intent nothing comes back. I thought it might be due to the free account limitations, but the error continues after adding billing information and upgrading.

Or, rather, the lack of error with no response generated. Is there something I'm missing in here?


Solution

  • The issue: The DialogFlow agent was returning a response before the Opensearch results had been processed.

    Solution: First, route the chat intents without using the built in .handleRequest method for the intent which makes a call to Opensearch, returning a promise.

    function chooseHandler(agent) {
        var intentRoute = "";
        switch (agent.intent) {
            case "Default Welcome Intent": 
                intentRoute = "welcome";
                break;
            case "Default Fallback Intent": 
                intentRoute = "fallback";
                break;
            case "Catalog Search":
                intentRoute = "catalog";
                break;
        }
        return new Promise ((resolve, reject) => {
            if (intentRoute.length > 0) {
                resolve(intentRoute);
            }
            else {
                reject(Error("Route Not Found"));
            }
        });
    }
    
    function assignResponse(intentRoute, agent) {
        switch (intentRoute) {
            case "welcome":
                agent.handleRequest(welcome);
                break;
            case "fallback":
                agent.handleRequest(fallback);
                break;
            case "catalog":
                catalog_response();
                break;
            default:
                Error("Intent not found");
        }
    
    } 
    

    Next, retrieve the data from the OpenSearch API, returning a promise.

    function catalog_search(agent) {
        var itemSubject = agent.parameters["item-subject"] ? "+" + agent.parameters["item-subject"] : "";
        var itemTitle = agent.parameters["item-title"] ? "+" + agent.parameters["item-title"] : "";
        var itemType = agent.parameters["item-type"] ? "+" + agent.parameters["item-type"] : "";
        var authorName = "";
        if (agent.parameters["author-name"]) {
           authorName = agent.parameters["author-name"]["name"] ? "+" + agent.parameters["author-name"]["name"] + " " + agent.parameters["author-name"]["last-name"] : "+" + agent.parameters["author-name"]["last-name"];
        }
        var searchString = "";
        if (itemSubject.length > 0) { searchString += itemSubject; }
        if (itemTitle.length > 0 ) { searchString += itemTitle; }
        if (authorName.length > 0) { searchString += authorName; }
        if (itemType.length > 0) { searchString += itemType; }
        var url = "https://gapines.org/opac/extras/opensearch/1.1/-/rss2-full?searchTerms=site(GCHR-CCO)" + encodeURIComponent(searchString) + "&searchClass=keyword";
        console.log(url);
        console.log(encodeURIComponent(searchString));
    
        var itemList = [];
        var chatResponse = "this was untouched";
    
        return new Promise((resolve, reject) => {
            catalogSearch.load(url, (err, rss) => {
                if (err) {
                    reject(Error("url failed!"));
                }
                else {
                    itemList = rss;
                    if (itemList.items.length > 0) {
                        if (itemList.items.length === 1) {
                            chatResponse = "The only item found for that search is ";
                        }
                        else if (itemList.items.length < 10) {
                            chatResponse = "The first few items in your search are ";
                        }
                        else {
                            chatResponse = "The first ten items in your search are ";
                        }
                        itemList.items.forEach( (item, index) => {
                           chatResponse = chatResponse + item.title.replace(/\//g, "").trim();
                           if (index + 1 < itemList.items.length) {
                               chatResponse += ", ";
                           } else {
                               chatResponse += ".";
                           }
                        });
                    }
                    else {
                        chatResponse = "We're sorry, there are no items matching that search at our library!";
                    }
                    console.log(rss);
                    resolve(chatResponse);
                }
            });
        });
    }
    

    Create a properly-formatted JSON response to the intent, and send it back.

    function catalog_response() {
        catalog_search(agent).then((response) => {
            res.json({ "fulfillmentText": response });
            res.status(200).send();
            return 1;
        }), (error) => {
            console.log("failed", error);
        };
    }