简体   繁体   English

Dialogflow + 外部 API + Google Cloud Functions *无 * Firebase:如何返回履行响应?

[英]Dialogflow + external API + Google Cloud Functions *without* Firebase: how to return fulfillment response?

I'm trying to build a Dialogflow chatbot that fetches data from an external API via Google Cloud Functions, but without using Firebase.我正在尝试构建一个 Dialogflow 聊天机器人,它通过 Google Cloud Functions 从外部 API 获取数据,但使用 Firebase。 Despite extensive searching, I have not found any good examples or templates of this;尽管进行了大量搜索,但我还没有找到任何好的示例或模板; it seems like all the examples available use Firebase functions.似乎所有可用的示例都使用 Firebase 函数。

I'm a newbie programmer and unfamiliar with Node.js, Promises and all that fancy stuff, but I have gathered that accessing an external API through Dialogflow should be possible even without Firebase (I'm using a paid version of Google Cloud).我是一个新手程序员,不熟悉 Node.js、Promises 和所有那些花哨的东西,但我发现即使没有 Firebase(我使用的是付费版本的 Google Cloud)也应该可以通过 Dialogflow 访问外部 API。

I tried to create my Google Cloud Function using this Dialogflow weather API example, which is the closest thing I could find to what I need, even though this too uses Firebase: https://github.com/dialogflow/fulfillment-weather-nodejs/blob/master/functions/index.js#L72我尝试使用这个 Dialogflow 天气 API 示例创建我的 Google Cloud Function,这是我能找到的最接近我需要的东西,即使它也使用 Firebase: https : //github.com/dialogflow/fulfillment-weather-nodejs /blob/master/functions/index.js#L72

The problem is that my code fails somewhere around the "res.on('end'..." line and I can't figure out why. The Google Cloud Stackdriver log only gives the rather uninformative message "Ignoring exception from a finished function", but does not tell me what the exception is.问题是我的代码在“res.on('end'...”行附近的某个地方失败了,我不知道为什么。谷歌云 Stackdriver 日志只给出了相当无信息的消息“忽略来自已完成函数的异常",但没有告诉我例外是什么。

Here is an edited version of my index.js code:这是我的 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

So my question is: how can I make this code work to return the fulfillment response to Dialogflow?所以我的问题是:如何使此代码工作以将履行响应返回给 Dialogflow? The default welcome and fallback responses do come through from Google Cloud Functions, but my custom intent webhook response does not (even though "Enable webhook call" is set in Dialogflow for my.search).默认的欢迎和回退响应确实来自 Google Cloud Functions,但我的自定义意图 webhook 响应没有(即使在 Dialogflow 中为 my.search 设置了“启用 webhook 调用”)。

I did have the same issue and as you said I think is related to promises and asynchronous behavior of JavaScript.我确实有同样的问题,正如你所说,我认为这与 JavaScript 的承诺和异步行为有关。 Because when you call a cloud function this executes and then response, but this function doesn't wait for external API call.因为当您调用云函数时,它会执行然后响应,但该函数不会等待外部 API 调用。

I tried request client, but when I saw the logs view, the external api response after cloud function response.我尝试了请求客户端,但是当我看到日志视图时,云函数响应后的外部api响应。

So I choose use axios (Promise based HTTP client for node.js), then the cloud function works.所以我选择使用axios (用于 node.js 的基于 Promise 的 HTTP 客户端),然后云功能就可以工作了。

This is a simple example for Dialogflow + external API + Google Cloud Functions:这是 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);
});

Remember also add axios package to package.json :请记住还要将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"
  }
}

Finally this is a post http query that I made, maybe you find it useful.最后这是我做的一个 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);
    });
}

Some images from Dialogflow: Dialogflow 中的一些图片:

问候互联网

云功能测试

There may be other issues (I didn't read your code too carefully), but one is that although you're doing async operations, and returning a Promise in your call to getMyInfo() , you also need to have the Intent Handler searchMyInfo() return a Promise.可能还有其他问题(我没有仔细阅读您的代码),但其中之一是,尽管您正在执行异步操作,并在对getMyInfo()调用中返回 Promise,但您还需要拥有 Intent Handler searchMyInfo()返回一个 Promise。 This is so the handler dispatcher knows to wait for the promise to be completed before returning the response.这样处理程序调度程序知道在返回响应之前等待承诺完成。

It also looks a little... odd... the way you're handling the response.它看起来也有点......奇怪......你处理响应的方式。 Once you're using the dialogflow-fulfillment library, you should probably use it to generate the JSON (using agent.add() ), rather than trying to send the JSON yourself.一旦您使用了 dialogflow-fulfillment 库,您可能应该使用它来生成 JSON(使用agent.add() ),而不是尝试自己发送 JSON。 I didn't test this, but it could be that trying to send the JSON yourself, and then having the library try to set the JSON, could result in invalid JSON that Dialogflow is rejecting.我没有对此进行测试,但可能是因为尝试自己发送 JSON,然后让库尝试设置 JSON,可能会导致 Dialogflow 拒绝的无效 JSON。

After a lot of trial and error, I came up with this index.js that works for my particular use case as far as I've been able to test it.经过大量的反复试验,我想出了这个 index.js,只要我能够测试它,它就适用于我的特定用例。 I'm including it here in case someone else wants to try it out with a different API.我将它包含在这里以防其他人想使用不同的 API 进行尝试。 If you do test it, please comment here!如果您确实测试过,请在此处发表评论! I would be interested in knowing how it works for another case.我很想知道它如何适用于另一个案例。

 /** * 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM