我试图构建一个对话流聊天机器人,通过谷歌云函数从外部应用编程接口获取数据,但不使用Firebase。尽管进行了广泛的搜索,我还是没有找到任何好的示例或模板;似乎所有可用的示例都使用Firebase功能。
我是一个新手程序员,不熟悉Node。js、Promissions和所有这些花哨的东西,但我认为即使没有Firebase(我使用的是付费版本的Google Cloud),通过Dialogflow访问外部API也是可能的。
我尝试使用Dialogflow weather API示例创建我的Google Cloud函数,这是我能找到的最接近我需要的东西,尽管这也使用了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云函数,但我的自定义意图webhook响应没有(即使在Dialogflow中为my.search设置了“启用webhook调用”)。
经过反复试验,我得出了这个指数。就我所能测试的情况而言,它适用于我的特定用例。我把它包括在这里,以防其他人想用不同的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}¶meter_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
可能还有其他问题(我没有仔细阅读您的代码),但其中一个问题是,尽管您正在执行异步操作,并且在对getMyInfo()
的调用中返回promise,但您还需要让意图处理程序searchMyInfo()
返回promise。这使得处理程序调度器知道在返回响应之前等待promise完成。
它看起来也有点。。。古怪的你处理回应的方式。使用dialogflow实现库之后,您可能应该使用它来生成JSON(使用agent.add()
),而不是自己尝试发送JSON。我没有对此进行测试,但可能是尝试自己发送JSON,然后让库尝试设置JSON,会导致Dialogflow拒绝的JSON无效。
我也有同样的问题,正如你所说,我认为这与JavaScript的promise和异步行为有关。因为当您调用一个云函数时,它会执行然后响应,但这个函数不会等待外部API调用。
我尝试了请求客户端,但当我看到logs视图时,外部api响应在云函数响应之后。
所以我选择使用axios(基于promise的HTTP客户端node.js),然后云函数工作。
这是Dialogflow外部API Google Cloud Functions的一个简单示例:
索引。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
软件包添加到软件包中。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的图片:
尝试从web执行云函数终结点时,我收到以下错误: 我遵循了这个教程:https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/functions/helloworld/main.py 按此处所述调用函数时:https://cloud.google.com/functions/docs/writing/http,我收
我正试图通过谷歌云功能访问和更新我的firebase数据库,但它不起作用。 我已经编写了一个云函数,其中我已经初始化了Firebase-admin。 我必须提供service_account初始化管理。 我正在将我的firebase应用程序初始化为 我在谷歌云功能中没有我的服务帐户路径。 是否有任何方法从GOOGLE云函数访问Firebase? 谢谢
我尝试使用my console在谷歌云平台上部署云功能。我使用的命令是, 但我得到了这个错误, 错误:(gcloud.functions.deploy)操作错误:代码=3,消息=Build failed:无法解析存储源:googleapi:错误404:未找到,未找到 我试着在谷歌上搜索,但似乎以前没有人遇到过这个错误消息。我也尝试过改变项目,部署效果很好。 如果有人知道是什么导致了这个错误,以及我
当我运行gcloud函数deploy gcp_test——trigger resource xxx-test-123——trigger event google时。存储对象我发现语法错误。 错误:(gcloud.functions.deploy)操作错误:code=3,message=Function load错误:文件索引中的代码。无法加载js。你的代码中有语法错误吗?详细堆栈跟踪:/user\
我有一个应用引擎项目。 我也有谷歌云功能。 我想从App Engine项目中调用谷歌云功能。我就是没法让它发挥作用。 是的,如果我将函数完全公开(即将云函数设置为“允许所有流量”,并为“所有用户”创建一个允许调用函数的规则),它就可以工作。但是如果我限制这两个设置中的任何一个,它会立即停止工作,我得到403。 应用程序和函数在同一个项目中,所以我至少假设将函数设置为“仅允许内部流量”应该可以正常工
谷歌云的功能似乎非常有趣,因为它是无服务器和零维护的解决方案。但是,什么时候在谷歌应用程序引擎上使用谷歌云功能合适呢?