从 webhook 运行 nodejs 响应的正确方法是什么?

What is correct way to respond from webhook running nodejs?

尝试实现 web-hook(使用 V2 dialogflow)运行 nodejs。收到回复 "MalformedResponse 'final_response' must be set."。下面是代码。在 POST (app.post) 代码块的末尾,期望 conv.close 将发送 SimpleResponse。但这并没有发生。需要帮助理解为什么会出现此错误以及可能的解决方向。

谢谢

const express = require('express');
const {
  dialogflow,
  Image,
  SimpleResponse,
} = require('actions-on-google')

const bodyParser = require('body-parser');
const request = require('request');
const https = require("https");
const app = express();
const Map = require('es6-map');

// Pretty JSON output for logs
const prettyjson = require('prettyjson');
const toSentence = require('underscore.string/toSentence');

app.use(bodyParser.json({type: 'application/json'}));

// http://expressjs.com/en/starter/static-files.html
app.use(express.static('public'));

// http://expressjs.com/en/starter/basic-routing.html
app.get("/", function (request, response) {
  console.log("Received GET request..!!");
  //response.sendFile(__dirname + '/views/index.html');
  response.end("Response from my server..!!");
});

// Handle webhook requests
app.post('/', function(req, res, next) {
  console.log("Received POST request..!!");
  // Log the request headers and body, to aide in debugging. You'll be able to view the
  // webhook requests coming from API.AI by clicking the Logs button the sidebar.
  console.log('======Req HEADERS================================================');    
  logObject('Request headers: ', req.headers);
  console.log('======Req BODY================================================');    
  logObject('Request body: ', req.body);
  console.log('======Req END================================================');    

  // Instantiate a new API.AI assistant object.
  const assistant = dialogflow({request: req, response: res});

  // Declare constants for your action and parameter names
  //const PRICE_ACTION = 'price';  // The action name from the API.AI intent
  const PRICE_ACTION = 'revenue';  // The action name from the API.AI intent
  var price = 0.0

  // Create functions to handle intents here
  function getPrice(assistant) {
    console.log('** Handling action: ' + PRICE_ACTION);
    let requestURL = 'https://blockchain.info/q/24hrprice';
    request(requestURL, function(error, response) {
      if(error) {
        console.log("got an error: " + error);
        next(error);
      } else {        
        price = response.body;
        logObject('the current bitcoin price: ' , price);
        // Respond to the user with the current temperature.
        //assistant.tell("The demo price is " + price);
      }
    });
  }

  getPrice(assistant); 

  var reponseText = 'The demo price is ' + price;

  // Leave conversation with SimpleResponse 
  assistant.intent(PRICE_ACTION, conv => {
    conv.close(new SimpleResponse({
     speech: responseText,
     displayText: responseText,
    })); 
  });

}); //End of app.post

// Handle errors.
app.use(function (err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Oppss... could not check the price');
})

// Pretty print objects for logging.
function logObject(message, object, options) {
  console.log(message);
  console.log(prettyjson.render(object, options));
}

// Listen for requests.
let server = app.listen(process.env.PORT || 3000, function () {
  console.log('Your app is listening on ' + JSON.stringify(server.address()));
});

一般来说,"final_response" must be set错误是因为你没有发回任何东西。您的代码中发生了很多事情,当您走在正确的轨道上时,代码中的一些事情可能会导致此错误。

首先 - 在代码中,您似乎对如何发送响应感到困惑。您同时调用了 conv.close() 和注释掉的 assistant.tell()conv.close()conv.ask() 方法是使用此版本库发送回复的方式。 tell() 方法被以前的版本使用,不再支持。

接下来,您的代码看起来只是在调用路由函数时设置助手对象。虽然可以做到这一点,但这不是通常的做法。通常,您将创建助手对象并设置 Intent 处理程序(使用 assistant.intent())作为程序初始化的一部分。这大致相当于在请求本身进入之前设置 Express 应用程序及其路由。

设置助手然后将其挂接到路由的部分可能如下所示:

const assistant = dialogflow();
app.post('/', assistant);

如果你真的想先检查请求和响应对象,你可以这样做

const assistant = dialogflow();
app.post('/', function( req, res ){
  console.log(JSON.stringify(req.body,null,1));
  assistant( req, res );
});

与此相关的似乎是您试图在路由处理程序中执行代码,然后尝试调用意图处理程序。同样,这可能是可能的,但不是建议的使用库的方式。 (而且我还没有尝试调试您的代码以查看您的操作方式是否存在问题以查看您是否有效地进行了操作。)更典型的是从 [=47 调用 getPrice() =]inside Intent 处理程序,而不是尝试从路由处理程序内部调用它。

但这会导致另一个问题。 getPrice()函数调用request(),属于异步调用。异步调用是导致空响应的最大问题之一。如果您使用的是异步调用,则您 必须 return 一个 Promise。将 Promise 与 request() 一起使用的最简单方法是改用 request-promise-native 包。

因此该代码块可能看起来(非常粗略地)像这样:

const rp = require('request-promise-native');

function getPrice(){
  return rp.get(url)
    .then( body => {
      // In this case, the body is the value we want, so we'll just return it.
      // But normally we have to get some part of the body returned
      return body;
    });
}

assistant.intent(PRICE_ACTION, conv => {
  return getPrice()
    .then( price => {
      let msg = `The price is ${price}`;
      conv.close( new SimpleResponse({
        speech: msg,
        displayText: msg
      });
    });
});

关于 getPrice() 和意图处理程序需要注意的重要一点是它们 都是 return 一个 Promise。

最后,您的代码中有一些奇怪的方面。 res.status(500).send('Oppss... could not check the price'); 之类的行可能不会按照您认为的那样进行。例如,它不会发送要说出的消息。相反,智能助理只会关闭连接并说出了点问题。

非常感谢@Prisoner。以下是基于上述评论的 V2 工作解决方案。同样已在 nodejs webhook(没有 firebase)上得到验证。代码的 V1 版本引用自 https://glitch.com/~aog-template-1

编码愉快..!!

// init project pkgs
const express = require('express');
const rp = require('request-promise-native');
const {
  dialogflow,
  Image,
  SimpleResponse,
} = require('actions-on-google')

const bodyParser = require('body-parser');
const request = require('request');
const app = express().use(bodyParser.json());

// Instantiate a new API.AI assistant object.
const assistant = dialogflow();

// Handle webhook requests
app.post('/', function(req, res, next) {
  console.log("Received POST request..!!");
  console.log('======Req HEADERS============================================');    
  console.log('Request headers: ', req.headers);
  console.log('======Req BODY===============================================');    
  console.log('Request body: ', req.body);
  console.log('======Req END================================================');    

  assistant(req, res);

});

// Declare constants for your action and parameter names
const PRICE_ACTION = 'revenue';  // The action name from the API.AI intent
var price = 0.0

// Invoke http request to obtain blockchain price
function getPrice(){
  console.log('getPrice is invoked');
  var url = 'https://blockchain.info/q/24hrprice';
  return rp.get(url)
    .then( body => {
      // In this case, the body is the value we want, so we'll just return it.
      // But normally we have to get some part of the body returned
      console.log('The demo price is ' + body);
      return body;
    });
}

// Handle AoG assistant intent
assistant.intent(PRICE_ACTION, conv => {
  console.log('intent is triggered');
  return getPrice()
    .then(price => {
      let msg = 'The demo price is ' + price;
      conv.close( new SimpleResponse({
        speech: msg,
      }));
   });
});

// Listen for requests.
let server = app.listen(process.env.PORT || 3000, function () {
  console.log('Your app is listening on ' + JSON.stringify(server.address()));
});