Как управлять пределом времени ожидания ответа 5 секунд в Dialogflow / Api.ai?

Я использую Dialogflow для создания агента / бота, который отвечает на различные типы пользовательских запросов с элементами действий, такими как "Мне нужно получить письмо от HR для подтверждения адреса". Для этого бот должен получить некоторую информацию из базы данных компании и сгенерировать документ / письмо, заполнив эту извлеченную информацию в шаблонном файле письма, предоставленном отделом кадров. Логика для выполнения этого действия уже записана в файле Python. Интеграция базы данных осуществляется с помощью Webhooks.

Проблема заключается в том, что этот полный процесс интерпретации запроса пользователя, открытия базы данных и получения необходимой информации занимает более 5 секунд, что является пределом времени ожидания ответа для агентов Dialogflow. Я провел некоторое исследование по этому вопросу и обнаружил, что мы не можем увеличить этот предел, но мы можем поддерживать сеанс с помощью асинхронных вызовов. Я не могу найти правильный ресурс, который дает ответ.

Итак, мои вопросы

Можем ли мы сделать асинхронные вызовы в диалоге?

Если да, то как мы можем отправлять асинхронные данные через json агенту Dailogflow?

Есть ли другой способ справиться с этим 5-секундным пределом времени ожидания ответа?

Заранее спасибо!

1 ответ

Решение

Я только что проверил документацию, и действительно, есть 5-секундный лимит времени ожидания.

Это может быть не самым хорошим решением и может не соответствовать вашему случаю, но учитывая строгое 5-секундное окно (мы хотим обеспечить динамичный разговор, не рискуя тем, что пользователь ждет слишком долго)

Вы начинаете вычисление с вашим первым намерением асинхронно и возвращаетесь к пользователю и просите его запросить результаты через несколько секунд, в то время как вычисление завершено. Он будет сохранен в личном пространстве для пользователя, после чего пользователь вызовет второе намерение, которое будет запрашивать результаты, которые тем временем будут предварительно вычислены, так что вы можете просто получить и вернуть их.

Вы можете продлить 5-секундный предел намерения до 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

Уменьшите сложность своего кода, чтобы сделать его быстрее, если вы используете микросервисную или нано-сервисную архитектуру, такую ​​как функция firebase, aws lambda или kubernatees, пытаясь уменьшить мертвый запуск и холодный запуск, инициализируя библиотеки внутри функции вместо глобальной области,

Если у вас есть несколько вызовов api, попробуйте сделать это параллельно, а не один за другим, чтобы уменьшить. например, подход обещания.

Вы также можете решить проблему через базу данных или контекст

например, пользователь спрашивает: каков мой баланс

Бот: Проверяю ваш баланс, спросите через несколько секунд снова

И получите время, занимающее api в фоновом режиме, и сохраните данные в высокоскоростной базе данных, такой как mongodb (относительно выше, чем у медленных API веб-служб), и отметьте флаг в контекстном меню или базе данных

Когда пользователь снова спросит через несколько секунд, просто проверьте флаг, если он положительный, получите данные из высокоскоростной базы данных и передайте их пользователю.

Совет: если вы используете помощник Google, вы можете отправлять push-уведомления, когда получение данных из API завершено.

Обновить:

Ответ на комментарий: "Не могли бы вы объяснить, что вы имеете в виду под" инициализацией библиотек внутри функции вместо глобальной области видимости "?"

Например, в случае функций firebase он фактически выполнялся в контейнерной среде, и когда вы не вызываете функцию какое-то время, она просто освобождает контейнер вашей функции из памяти, и когда вы вызываете его снова, он снова инициализирует контейнер перед фактическое выполнение, эта инициализация называется холодным запуском, поэтому для первого вызова требуется немного больше времени, а для последующего вызова требуется меньше, даже время выполнения для первого вызова такое же, но функция не может отложить выполнение до завершения инициализации контейнера, инициализации of container включает в себя всю библиотеку, инициализацию подключения к базе данных и все такое. Все в порядке, вы не можете избавиться от холодного старта в архитектуре микро / наносервисов, но иногда это занимает все больше и больше времени и вызывает разочарование и плохой опыт для пользователя.и службы, такие как первый вызов диалогового потока, просто терпят неудачу Каждый раз, что нехорошо, вот больше: службы, такие как firebase, фактически создают отдельный контейнер для каждой функции, например, если у вас есть несколько функций, 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
  2. функция регистрации http, использует mongodb
  3. триггер базы данных для ввода прописных букв без использования базы данных
  4. функция HTTP-триггера ping, которая не использует никакую базу данных

вы также можете заметить две инициализации базы данных, firebase и mongodb

скажем, когда вы вызываете функцию в первый раз через некоторое время, а функция холодная, поэтому она инициализирует эти две базы данных не только один раз, но и для всех четырех функций по отдельности, допустим, каждая инициализация базы данных занимает 400 миллисекунд, поэтому эти две будут 800 мс, поэтому, когда вы вызовете первую функцию для добавления сообщения, она инициализирует оба db(800 мс), тогда она фактически выполнит функцию (скажем, 150 мс), так что 800 мс +150 мс, поэтому в первый раз потребуется около 950 мс независимо от того, что он не использует mongodb, он инициализирует его, потому что инициализация записывается в глобальной области

если вы вызываете функцию регистрации сразу после функции addMessage, она будет делать те же 800 мс для инициализации db, а затем выполнение функции регистрации позволяет сказать, что требуется 200 мс, итого 800+200=1000 мс, вы можете подумать, что db уже инициализирован, так почему еще раз, как я уже упоминал в своем первоначальном ответе, каждая функция может находиться в отдельном контейнере (не всегда, но это правда), это означает, что функция регистрации может не знать, что происходит в функции addMessage, поэтому она также инициализирует db для этого контейнера, поэтому сначала звонок займет больше времени, чем последующие звонки

функция 3 - это триггер базы данных, и он не использует базу данных, но когда он вызывается, он получает дескриптор базы данных и использует этот дескриптор для внесения изменений в базу данных, но в этом случае, когда функция холодная, и вы делаете запись в db он фактически инициализирует функцию, как и любую другую функцию, что означает, что накладные расходы 800 мс все еще присутствуют впервые, и это та самая причина, по которой большинство людей ненавидят триггеры db, но они не знают, почему это происходит (на этом этапе я хотел бы упомянуть, что в их дизайне есть несколько вещей, кроме холодного запуска, и есть проблемы с github, но поверьте мне, оптимизация холодного запуска решит вашу проблему на 50%)

функция 4 - это не что иное, как функция ping, но она также инициализирует базу данных, накладные расходы 800 мс зря

теперь взгляните на следующий код с некоторыми оптимизациями:

вы можете заметить, что вместо инициализации db непосредственно в глобальной области я зарегистрировал функцию подпрограммы в глобальной области с именем initMongodb, содержащую логику инициализации db, поэтому, когда вы вызываете функцию firebase, она не будет инициализировать базу данных во время холодного запуска, а просто зарегистрирует эту функцию подпрограммы в глобальной области видимости, поэтому вы сможете получить к нему доступ любой функции firebase,

теперь, если вы наблюдаете вторую функцию, которая является подпиской, вы, возможно, заметили, что я сделал инициализацию db еще более условной, потому что, если функция не получает правильные данные для выполнения подписки, в чем смысл инициализации базы данных, на этом этапе я хотел бы упомянуть что если инициализация базы данных выполняется один раз, то в последующих вызовах она фактически не инициализирует базу данных снова, фактически, когда выполнение функции firebase завершается, он уничтожает все переменные в этой области функций firebase, но сохраняет глобальные переменные (до следующего холодного запуска) и вы можете заметить, что мне нужен mongodb в качестве имени varibale mongoose и firebase как varibale с именем admin в глобальной области видимости и инициализации вносит некоторые изменения в эти переменные и все, поэтому логика инициализации является условной: если db не инициализирован, то инициализация, в противном случае ничего не делает.

еще один момент, который следует отметить здесь, - это "не" пытаться сохранить все вещи внутри локальной области действия функции firebase (например, импорт мангуста и инициализация мангуста и других БД), это сделает накладные расходы постоянными и будет импортировать и инициализировать базу данных каждые вызов с нуля, так как вся локальная переменная уничтожается после завершения выполнения, поэтому это даже более опасно, чем сам холодный запуск

и, наконец, если вы наблюдаете функции 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
Другие вопросы по тегам