【问题标题】:Dialogflow + external API + Google Cloud Functions *without* Firebase: how to return fulfillment response?Dialogflow + 外部 API + Google Cloud Functions *without* Firebase:如何返回履行响应?
【发布时间】:2019-03-11 16:03:40
【问题描述】:

我正在尝试构建一个 Dialogflow 聊天机器人,它通过 Google Cloud Functions 从外部 API 获取数据,但没有使用 Firebase。尽管进行了广泛的搜索,但我没有找到任何好的示例或模板;似乎所有可用的示例都使用 Firebase 函数。

我是一名新手程序员,不熟悉 Node.js、Promises 和所有花哨的东西,但我发现即使没有 Firebase,也应该可以通过 Dialogflow 访问外部 API(我使用的是付费版本的 Google云)。

我尝试使用这个 Dialogflow 天气 API 示例创建我的 Google Cloud Function,这是我能找到的最接近我需要的东西,即使它也使用 Firebase: https://github.com/dialogflow/fulfillment-weather-nodejs/blob/master/functions/index.js#L72

问题是我的代码在“res.on('end'...”行附近的某个地方失败了,我不知道为什么。Google Cloud Stackdriver 日志只给出了相当无信息的消息“忽略来自的异常一个完成的函数”,但没有告诉我异常是什么。

这是我的 index.js 代码的编辑版本:

'use strict';

const rpn = require('request-promise-native'); 
const http = require('http');
const hostAPI = 'my host API URL goes here';
const url = require('url');
const {WebhookClient} = require('dialogflow-fulfillment');

exports.myGoogleCloudSearch = (req, res) => {
  	const agent = new WebhookClient({request: req, response: res}); // Dialogflow agent
	// These are logged in Google Cloud Functions
	console.log('Dialogflow Request headers: ' + JSON.stringify(req.headers));
  	console.log('Dialogflow Request body: ' + JSON.stringify(req.body));
  
  	// Default welcome intent, this comes through to Dialogflow
  	function welcome(agent) {
    	agent.add('This welcome message comes from Google Cloud Functions.');
    }

  	// Default fallback intent, this also comes through
  	function fallback(agent) {
    	agent.add('This is the fallback response from Google Cloud Functions.');
    }
		
 	function searchMyInfo(agent) {
    	// get parameters given by user in Dialogflow
      	const param1 = agent.parameters.param1;
      	const param2 = agent.parameters.param2;
      	const param3 = agent.parameters.param3
		// this is logged
      	console.log('Parameters fetched from Dialogflow: ' + param1 + ', ' + param2 + ', ' + param3);
      	
       	var myUrl = hostAPI + param1 + param2 + param3;
		// the URL is correct and also logged
       	console.log('The URL is ' + myUrl);
		
		// Everything up to here has happened between Dialogflow and Google Cloud Functions
		// and inside GCF, and up to here it works
		
		// Next, contact the host API to get the requested information via myUrl
		// Using this as an example but *without* Firebase:
		// https://github.com/dialogflow/fulfillment-weather-nodejs/blob/master/functions/index.js#L41
		
		function getMyInfo(param1, param2, param3) {
			console.log('Inside getMyInfo before Promise'); // this is logged
			return new Promise((resolve, reject) => {
				console.log('Inside getMyInfo after Promise'); // this is logged
				console.log('Should get JSON from ' + myUrl);
				rpn.get(myUrl, (res) => {
					// The code is run at least up to here, since this is logged:
					console.log('Inside rpn.get');
					
					// But then the Google Cloud log just says 
					// "Ignoring exception from a finished function"
					// and nothing below is logged (or run?)
					
					let body = ''; // variable to store response chunks
					res.on('data', (chunk) => {body += chunk;}); // store each response chunk
					res.on('end', () => {
						// this is not logged, so something must go wrong here
						console.log('Inside res.on end block');
						
						// Parse the JSON for desired data
						var myArray = JSON.parse(body); // fetched JSON parsed into array
						console.log(myArray); // not logged
						
						// Here I have more parsing and filtering of the fetched JSON
						// to obtain my desired data. This JS works fine for my host API and returns
						// the correct data if I just run it in a separate html file,
						// so I've left it out of this example because the problem seems
						// to be with the Promise(?).
						
						// Create the output from the parsed data
						// to be passed on to the Dialogflow agent

						let output = agent.add('Parsed data goes here');
						console.log(output);
						resolve(output); // resolve the promise
					}); // res.on end block end
					
					// In case of error
					res.on('error', (error) => {
						// this is not logged either
						console.log('Error calling the host API');
						reject();
					}); // res.on error end
				}); // rpn.get end 
			}); // Promise end
		} // getMyInfo end
		
		// call the host API: this does not seem to work since nothing is logged
		// and no error message is returned

		getMyInfo(param1, param2, param3).then((output) => {
			console.log('getMyInfo call started');
			// Return the results of the getMyInfo function to Dialogflow
			res.json({'fulfillmentText': output});
		}).catch(() => {
			// no error message is given either
			res.json({'fulfillmentText' : 'There was an error in getting the information'});
			console.log('getMyInfo call failed');
		});
		
    } // searchMyInfo(agent) end
  
  	// Mapping functions to Dialogflow intents
  	let intentMap = new Map();
  	intentMap.set('Default Welcome Intent', welcome); // this works
  	intentMap.set('Default Fallback Intent', fallback); // this works
  	intentMap.set('my.search', searchMyInfo); // this does not work
  	agent.handleRequest(intentMap);

}; // exports end

所以我的问题是:我怎样才能使此代码工作以将履行响应返回给 Dialogflow?默认的欢迎和回退响应确实来自 Google Cloud Functions,但我的自定义意图 webhook 响应没有(即使在 Dialogflow 中为 my.search 设置了“启用 webhook 调用”)。

【问题讨论】:

    标签: javascript node.js google-cloud-functions dialogflow-es dialogflow-es-fulfillment


    【解决方案1】:

    我确实遇到了同样的问题,正如你所说,我认为这与 JavaScript 的承诺和异步行为有关。因为当您调用云函数时,它会执行然后响应,但此函数不会等待外部 API 调用。

    我试过request客户端,但是当我看到日志视图时,云函数响应后的外部api响应。

    所以我选择使用axios(node.js 的基于 Promise 的 HTTP 客户端),然后云功能起作用。

    这是 Dialogflow + 外部 API + Google Cloud Functions 的简单示例:

    index.js

    'use strict';
    
    const functions = require('firebase-functions');
    const { WebhookClient } = require('dialogflow-fulfillment');
    const { Card, Suggestion } = require('dialogflow-fulfillment');
    var axios = require("axios");
    
    process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
    
    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));
    
      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 helloWorld() {
        return axios({
          method: "GET",
          url: "https://run.mocky.io/v3/197de163-acc3-4c86-a13f-79314fe9da04",
          data: "",
        })
          .then((response) => {
            console.log(response.data.body.greeting); //Hello World
            agent.add(response.data.body.greeting); 
          })
          .catch((error) => {
            console.log(error);
          });
      }
    
      let intentMap = new Map();
      intentMap.set('Default Welcome Intent', welcome);
      intentMap.set('Default Fallback Intent', fallback);
      intentMap.set('greeting.helloWorld', helloWorld);
    
      agent.handleRequest(intentMap);
    });
    

    记住还要将axios 包添加到package.json

    {
      "name": "dialogflowFirebaseFulfillment",
      "description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase",
      "version": "0.0.1",
      "private": true,
      "license": "Apache Version 2.0",
      "author": "Google Inc.",
      "engines": {
        "node": "10"
      },
      "scripts": {
        "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
        "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
      },
      "dependencies": {
        "actions-on-google": "^2.2.0",
        "firebase-admin": "^5.13.1",
        "firebase-functions": "^2.0.2",
        "dialogflow": "^0.6.0",
        "dialogflow-fulfillment": "^0.5.0",
        "axios": "^0.20.0"
      }
    }
    

    最后这是我做的一个post http查询,也许你觉得有用。

    function consulta() {
      // console.log(agent.parameters.consulta);
      // console.log(agent.query);
    
      var consulta = agent.query.replace(/(\r\n|\n|\r)/gm, " ");
    
      return axios({
        method: "POST",
        url: "http://jena-fuseki-api:3030/Matricula",
        headers: {
          Accept: "application/sparql-results+json,*/*;q=0.9",
          "Content-Type": "application/x-www-form-urlencoded",
        },
        params: {
          query: consulta,
        },
      })
        .then((response) => {
          var elements = response.data.results.bindings;
    
          for (var i = 0; i < elements.length; i++) {
            var result = "";
            var obj = elements[i];
            var j = 0;
            var size = Object.size(obj);
            for (var key in obj) {
              var attrName = key;
              var attrValue = obj[key].value;
              result += attrName + ": " + attrValue;
              if (j < size - 1) result += " | ";
              j++;
            }
            console.log(result);
            agent.add(result);
          }
          console.log("----------------------------");
        })
        .catch((error) => {
          console.log("Failed calling jena-fuseki API");
          console.log(error);
        });
    }
    

    Dialogflow 中的一些图片:

    【讨论】:

      【解决方案2】:

      可能还有其他问题(我没有仔细阅读您的代码),但一个是虽然您正在执行异步操作,并在您对getMyInfo() 的调用中返回一个 Promise,但您还需要拥有Intent Handler searchMyInfo() 返回一个 Promise。这样处理程序调度程序就知道在返回响应之前等待承诺完成。

      它看起来也有点……奇怪……你处理响应的方式。一旦您使用了 dialogflow-fulfillment 库,您可能应该使用它来生成 JSON(使用 agent.add()),而不是尝试自己发送 JSON。我没有对此进行测试,但可能是尝试自己发送 JSON,然后让库尝试设置 JSON,可能会导致 Dialogflow 拒绝的无效 JSON。

      【讨论】:

      • 非常感谢!我确信我的代码看起来很奇怪,因为它是从许多我还不完全理解的不同来源拼凑而成的。我需要关于 Node 和 Promises 的速成课程...但只是为了确保是否可以在没有 Firebase 的情况下使 Dialogflow 调用第三方 API 工作?我开始怀疑这一点,因为我在网上找不到任何来源或示例。
      • 当然。您根本不需要 Firebase。 StackOverflow 和其他地方确实 使用 Firebase 的大多数示例在任何其他平台上的工作原理应该基本相同 - 特别是当您完成有关初始化 dialogflow-fulfillment 库的部分时。所以如果你只看一个特定的 Intent Handler 是如何工作的,它应该可以作为任何平台的示例。
      【解决方案3】:

      经过大量试验和错误,我想出了这个 index.js,它适用于我的特定用例,只要我能够测试它。我将它包含在此处,以防其他人想使用不同的 API 进行尝试。如果你测试它,请在这里评论!我很想知道它在另一个案例中是如何工作的。

      /**
       * Copyright 2017 Google Inc. All Rights Reserved.
       *
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *    http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the License for the specific language governing permissions and
       * limitations under the License.
       */
      
      'use strict';
      
      // Include nodejs request-promise-native package as dependency
      // because async API calls require the use of Promises
      const rpn = require('request-promise-native');
      const hostAPI = 'https://my.api.here/'; // root URL of the API
      const { WebhookClient } = require('dialogflow-fulfillment');
      
      exports.googleCloudSearch = (req, res) => {
          const agent = new WebhookClient({ request: req, response: res }); // Dialogflow agent
          console.log('Dialogflow Request headers: ' + JSON.stringify(req.headers)); // testing
          console.log('Dialogflow Request body: ' + JSON.stringify(req.body)); // testing
      
          // Default welcome intent
          function welcome(agent) {
              agent.add('Welcome to my chatbot!');
          }
      
          // Default fallback intent
          function fallback(agent) {
              agent.add('Sorry, I don\'t understand.');
          }
      
          // Default conversation end
          function endConversation(agent) {
              agent.add('Thank you and have a nice day!');
          }
      
          // Function for passing data to the myapi.search intent in Dialogflow
          function searchMyApi(agent) {
              return new Promise((resolve, reject) => {
                  // get parameters given by user in Dialogflow
                  const param1 = agent.parameters.param1;
                  const param2 = agent.parameters.param2;
                  // and so on...
      
                  console.log(`Parameters from Dialogflow: ${param1}, ${param2}`); // testing
      
                  // If necessary, format the parameters passed by Dialogflow to fit the API query string.
                  // Then construct the URL used to query the API.
      
                  var myUrl = `${hostAPI}?parameter_1=${param1}&parameter_2=${param2}`;
                  console.log('The URL is ' + myUrl); // testing
      
                  // Make the HTTP request with request-promise-native
                  // https://www.npmjs.com/package/request-promise
      
                  var options = {
                      uri: myUrl,
                      headers: {
                          'User-Agent': 'Request-Promise-Native'
                      },
                      json: true
                  };
      
                  // All handling of returned JSON data goes under .then and before .catch
                  rpn(options)
                      .then((json) => {
      
                          var result = ''; // the answer passed to Dialogflow goes here 
      
                          // Make a string out of the returned JSON object
                          var myStringData = JSON.stringify(json);
                          console.log(`This data was returned: ${myStringData}`); // testing
      
                          // Make an array out of the stringified JSON
                          var myArray = JSON.parse(myStringData);
                          console.log(`This is my array: ${myArray}`); // testing
      
                          // Code for parsing myArray goes here, for example:
      
                          if (condition) {
                              // For example, the returned JSON does not contain the data the user wants
                              result = agent.add('Sorry, could not find any results.');
                              resolve(result); // Promise resolved
                          }
                          else {
                              // If the desired data is found:
                              var output = ''; // put the data here
                              result = agent.add(`Here are the results of your search: ${output}`);
                              resolve(result); // Promise resolved
                          }
                      }) // .then end
                      .catch(() => { // if .then fails
                          console.log('Promise rejected');
                          let rejectMessage = agent.add('Sorry, an error occurred.');
                          reject(rejectMessage); // Promise rejected
                      });	// .catch end
              }); // Promise end
          } // searchMyApi end
      
          // Mapping functions to Dialogflow intents
          let intentMap = new Map();
          intentMap.set('Default Welcome Intent', welcome);
          intentMap.set('Default Fallback Intent', fallback);
          intentMap.set('End Conversation', endConversation);
          intentMap.set('myapi.search', searchMyApi);
          agent.handleRequest(intentMap);
      
      }; // exports end

      【讨论】:

        猜你喜欢
        • 2019-10-13
        • 2021-05-19
        • 1970-01-01
        • 2020-03-15
        • 2021-07-22
        • 2017-12-09
        • 2021-06-30
        • 2019-02-04
        • 2019-03-02
        相关资源
        最近更新 更多