13

我正在使用 Dialogflow 创建一个代理/机器人,它通过诸如“我需要从 HR 那里获得一封地址证明信”之类的操作项来响应不同类型的用户查询。这需要机器人从公司的数据库中获取一些信息,并通过将检索到的信息填充到人力资源提供的模板化信件文件中来生成文档/信件。执行此操作的逻辑已写入 python 文件。数据库集成是使用 Webhooks 完成的。

问题在于,这个解释用户请求、打开数据库和检索所需信息的完整过程需要超过 5 秒,这恰好是 Dialogflow 代理的响应超时限制。我对此进行了一些研究,发现我们无法增加此限制,但我们可以通过异步调用保持会话处于活动状态。我无法找到提供答案的正确资源。

所以,我的问题是——

我们可以在对话流中进行异步调用吗?

如果是,那么我们如何通过 json 向 Dailogflow 代理发送异步数据?

有没有其他方法可以解决这个 5 秒响应超时限制?

提前致谢!

4

3 回答 3

9

我刚刚检查了Actions on Google 文档Fulfillment 文档页面,确实有 5 秒的超时限制。

这可能不是最好的解决方案,也可能不适合您的情况,但考虑到给定的严格 5 秒窗口(我们希望确保动态对话,而不会冒用户等待太久的风险)

您以您的第一个意图异步开始计算,然后返回给用户并告诉他们在几秒钟内请求结果,同时计算完成。它将保存在用户的私人空间中,此时用户将触发第二个意图,该意图将请求同时已预先计算的结果,因此您可以获取并返回它们。

于 2018-08-14T20:07:20.103 回答
5

您可以通过设置多个后续事件将 5 秒的 Intent 限制延长至 15 秒。目前只能依次设置3个后续事件(可以将超时时间延长至15秒)。

以下是如何在履行中心执行此操作的示例:

  function function1(agent){
      //This function handles your intent fulfillment
      //you can initialize your db query here.
      //When data is found, store it in a separate table for quick search
      
      //get current date
      var currentTime = new Date().getTime(); 
      
      while (currentTime + 4500 >= new Date().getTime()) {
        /*waits for 4.5 seconds
          You can check every second if data is available in the database
          if not, call the next follow up event and do the 
          same while loop in the next follow-up event 
          (up to 3 follow up events)
        */
        
         /* 
         if(date.found){
            agent.add('your data here');//Returns response to user
         }
          */
        
      } 
      
      //add a follow-up event
      agent.setFollowupEvent('customEvent1'); 
      
      //add a default response (in case there's a problem with the follow-up event)
      agent.add("This is function1");
  }


  let intentMap = new Map();
  intentMap.set('Your intent name here', function1);;
  agent.handleRequest(intentMap);

要了解有关自定义事件的更多信息,请访问此页面:https ://dialogflow.com/docs/events/custom-events

于 2018-12-04T21:30:36.510 回答
2

降低代码的复杂性以使其更快;你们中的一些人正在使用微服务或纳米服务架构,例如 firebase 函数、AWS lambda 或 Kubernetes,尝试通过在函数内部而不是全局范围内初始化库来减少死启动和冷启动,

如果您有多个 API 调用,请尝试使其并行而不是一个接一个地减少。例如 promise.all 方法

您也可以通过数据库或上下文来解决问题。

例如用户问:我的余额是多少

Bot:我正在检查你的余额。几秒后再问

并在后台获取耗时API并将数据保存在MongoDB等高速数据库中(相对于慢速Web服务API),并在上下文菜单或数据库中标记一个标志。

当用户在几秒钟后再次询问时,检查标志是否是肯定的 从高速数据库中获取数据并将其提供给用户

提示:如果您使用谷歌助手,您可以在从 API 获取数据完成时发送推送通知

更新:

回复评论:“你能解释一下“在函数内部初始化库而不是全局范围”是什么意思吗?

例如,在firebase函数的情况下,它实际上是在容器化环境中执行的,当你暂时不调用函数时,它只是从内存中释放函数的容器,当你再次调用它时,它会再次初始化容器实际执行时,该初始化称为冷启动,因此第一次调用需要更多时间,后续调用需要更少,即使第一次调用的执行时间相同但函数无法执行直到容器初始化完成,初始化容器包括所有的库和数据库连接初始化和所有。这一切都很好,你无法摆脱微/纳米服务架构中的冷启动,但有时它会花费越来越多的时间,并且会给用户带来挫败感和糟糕的体验,

稍后我将尝试添加 firebase 函数代码示例。

更新 2:

这是代码示例

传统方式:

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as _cors from 'cors';
import firestore from './../db'
import * as mongoose from "mongoose";

const defaultApp = admin.initializeApp(functions.config().firebase)


const dbURI = `mongodb://xxxxxx:xxxxxx@ds00000.mlab.com:123456/mydb`;
// const dbURI = `mongodb://localhost:27017/mydb`;


mongoose.connect(dbURI, {
        useNewUrlParser: true, useUnifiedTopology: true
    }).catch(e => {
        console.log("mongo connection failed for reason: ", e);
    })



var cors = _cors({ origin: true });// set these options appropriately According to your case,
// see document: https://www.npmjs.com/package/cors#configuration-options
// true means allow everything


// http example
export const addMessage = functions.https.onRequest((req, res) => {
    const original = req.query.text;
    admin.database().ref('/messages').push({ original: original }).then(snapshot => {
        res.redirect(303, snapshot.ref);
    });
});


export const signup = functions.https.onRequest(async (req, res) => {

    ... signup stuff using mongodb

    res.send("user signed up");
})

//databse trigger example
export const makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite(event => {
        const original = event.data.val();
        console.log('Uppercasing', event.params.pushId, original);
        const uppercase = original.toUpperCase();
        return event.data.ref.parent.child('uppercase').set(uppercase);
    });

//cors example
export const ping = functions.https.onRequest(async (req, res) => {
    cors(req, res, () => {
        res.send("this is a function");
    })
})

在上面的代码中,你可能会注意到 4 个函数

  1. HTTP 触发器 addMessage 用于在 firebase DB 中添加消息
  2. HTTP 注册功能,使用 MongoDB
  3. 数据库触发器使条目大写,不使用任何数据库
  4. HTTP触发ping功能,不使用任何数据库

您可能还会注意到两个数据库初始化,firebase 和 MongoDB

假设您在一段时间后第一次调用一个函数并且该函数是冷的,所以它将初始化这两个数据库,不仅一次而且分别对所有四个函数进行初始化,假设每个数据库初始化需要 400 毫秒,所以这两个需要 800 英里,所以当你调用第一个函数来添加一条消息时,它会初始化两个 db(800ms) 然后它会实际执行函数(比如说 150ms) 所以 800ms+150ms 所以第一个大约需要 950ms时间,无论它没有使用 mongodb,它都会初始化它,因为初始化是在全局范围内编写的

如果您在 addMessage 函数之后调用注册函数,它将为 db init 执行相同的 800 毫秒,然后注册函数执行让我们说它需要 200 毫秒,所以总共 800+200=1000 毫秒,您可能会认为 db 已经初始化了,为什么再说一次,正如我在最初的回答中已经提到的那样,每个函数可能都存在于单独的容器中(并非总是如此,但确实如此)这意味着注册函数可能不知道 addMessage 函数中发生了什么,因此它也会首先为其容器初始化数据库通话将花费更多时间然后随后的通话

函数 3 是一个 db 触发器,它没有使用数据库,但是当它被调用时,它接收到数据库的句柄并使用该句柄在数据库中进行更改,但在这种情况下,当函数是冷的并且您在db 它实际上像任何其他函数一样初始化函数,这意味着第一次仍然存在 800ms 开销,这就是大多数人讨厌 db 触发器但他们不知道为什么会发生的原因(此时我想提到他们的设计中除了冷启动之外几乎没有什么东西,而且 github 上也有问题,但相信我优化冷启动会解决你的问题 50%)

函数 4 只不过是一个 ping 函数,但它也会初始化数据库,800 毫秒的开销无济于事

现在看一下经过一些优化的以下代码:

您可能会注意到,我没有在全局范围内直接初始化数据库,而是在全局范围内注册了一个名为 initMongodb 的子例程函数,其中包含数据库初始化逻辑,因此当您调用 firebase 函数时,它不会在冷启动期间初始化数据库,而只会注册此子例程函数在全局范围内,因此您将能够访问它的任何 firebase 功能,

现在,如果您观察第二个函数,即注册,您可能已经注意到我使数据库初始化进一步有条件,因为如果函数没有接收到正确的数据来执行注册,那么初始化数据库有什么意义,在这一点上我想提一下如果数据库初始化完成一次,那么在后续调用中它实际上不会再次初始化数据库,实际上当firebase函数执行完成时它会破坏该firebase函数范围内的所有变量,但它会保留全局变量(直到下一次冷启动)你可能会注意到我需要 mongodb 作为变量名mongoose,firebase 作为变量名admin在全局范围内,初始化对这些变量和所有变量进行了一些更改,这就是为什么初始化逻辑是有条件的,如果 db 未初始化,则初始化,否则什么也不做。

这里要注意的另一点是“不要”尝试将所有内容保留在 firebase 函数本地范围内(例如导入 mongoose 以及初始化 mongoose 和其他 DB),这将使开销永久化,并且每次导入和初始化数据库从头开始调用,因为所有局部变量在执行完成后都会被销毁,所以它比冷启动本身更危险

最后,如果您观察函数 3 和 4,将不会进行数据库初始化,但这并不意味着在冷启动和后续调用中会花费相同的时间,在诸如加载库的导入之类的过程中仍然会发生一些事情文件从磁盘到内存,但与 db 初始化(向互联网上的其他计算机发出 https/socket 请求)相比,这并不需要那么长的时间,导入都发生在同一台计算机上,但最好避免不必要的生产中的进口。

冷启动优化方式(推荐)

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as _cors from 'cors';
import firestore from './../db'
import * as mongoose from "mongoose";

const dbURI = `mongodb://xxxxxx:xxxxxx@ds00000.mlab.com:123456/mydb`;
// const dbURI = `mongodb://localhost:27017/mydb`;


export functions initFirebase(){
    if (admin.apps.length === 0) {
        console.log("initializing firebase database");
        admin.initializeApp(functions.config().firebase)
    }else{
        console.log("firebase is already initialized");
    }
}

export function initMongoDb() {

    if (mongoose.connection.readyState !== mongoose.STATES.connected
        && mongoose.connection.readyState !== mongoose.STATES.connecting) {

        console.log("initializing mongoose");

        mongoose.connect(dbURI, {
            useNewUrlParser: true, useUnifiedTopology: true
        }).catch(e => {
            console.log("mongo connection failed for reason: ", e);
        })
    } else {
        console.log("mongoose already connected: ", mongoose.STATES[mongoose.connection.readyState]);
    }
}



var cors = _cors({ origin: true });// set these options appropriately According to your case,
// see document: https://www.npmjs.com/package/cors#configuration-options
// true means allow everything


// http example
export const addMessage = functions.https.onRequest((req, res) => {
    initFirebase()

    const original = req.query.text;
    admin.database().ref('/messages').push({ original: original }).then(snapshot => {
        res.redirect(303, snapshot.ref);
    });
});



export const signup = functions.https.onRequest(async (req, res) => {
    

    if(req.body.name && req.body.email && req.body.password){
        initMongoDb();

        ... signup stuff using mongodb

        res.send("user signed up");
    }else{
        res.status(400).send("parameter missing");
    }
})

//database trigger example
export const makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite(event => {        
        const original = event.data.val();
        console.log('Uppercasing', event.params.pushId, original);
        const uppercase = original.toUpperCase();
        return event.data.ref.parent.child('uppercase').set(uppercase);
    });

//cors example
export const function3 = functions.https.onRequest(async (req, res) => {
    cors(req, res, () => {
        res.send("this is a function");
    })
})


Update: a ping call to function on start of mobile app or on page load in web also works well



Inzamam Malik, 
Web & Chatbot developer. 
malikasinger@gmail.com
于 2020-03-29T03:59:34.410 回答