簡體   English   中英

Dialogflow + 外部 API + Google Cloud Functions *無 * Firebase:如何返回履行響應?

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

我正在嘗試構建一個 Dialogflow 聊天機器人,它通過 Google Cloud Functions 從外部 API 獲取數據,但使用 Firebase。 盡管進行了大量搜索,但我還沒有找到任何好的示例或模板; 似乎所有可用的示例都使用 Firebase 函數。

我是一個新手程序員,不熟悉 Node.js、Promises 和所有那些花哨的東西,但我發現即使沒有 Firebase(我使用的是付費版本的 Google Cloud)也應該可以通過 Dialogflow 訪問外部 API。

我嘗試使用這個 Dialogflow 天氣 API 示例創建我的 Google Cloud Function,這是我能找到的最接近我需要的東西,即使它也使用 Firebase: https : //github.com/dialogflow/fulfillment-weather-nodejs /blob/master/functions/index.js#L72

問題是我的代碼在“res.on('end'...”行附近的某個地方失敗了,我不知道為什么。谷歌雲 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 的承諾和異步行為有關。 因為當您調用雲函數時,它會執行然后響應,但該函數不會等待外部 API 調用。

我嘗試了請求客戶端,但是當我看到日志視圖時,雲函數響應后的外部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 中的一些圖片:

問候互聯網

雲功能測試

可能還有其他問題(我沒有仔細閱讀您的代碼),但其中之一是,盡管您正在執行異步操作,並在對getMyInfo()調用中返回 Promise,但您還需要擁有 Intent Handler searchMyInfo()返回一個 Promise。 這樣處理程序調度程序知道在返回響應之前等待承諾完成。

它看起來也有點......奇怪......你處理響應的方式。 一旦您使用了 dialogflow-fulfillment 庫,您可能應該使用它來生成 JSON(使用agent.add() ),而不是嘗試自己發送 JSON。 我沒有對此進行測試,但可能是因為嘗試自己發送 JSON,然后讓庫嘗試設置 JSON,可能會導致 Dialogflow 拒絕的無效 JSON。

經過大量的反復試驗,我想出了這個 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM