首页 > 解决方案 > 使用 Inline Fulfillment 到 Firestore 的 Dialogflow - 将所有用户数据存储在一个文档中

问题描述

我们如何在每个聊天会话中将所有用户输入数据存储在一个文档中?

我试过这段代码:

'use strict';

const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

process.env.DEBUG = 'dialogflow:debug';

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
    const agent = new WebhookClient({ request, response });

    function getAge(agent) {
        let age = agent.parameters.age;
        db.collection("users").add({age: age});
    }

    function getLocation(agent) {
        let location = agent.parameters.location;
        db.collection("users").add({location: location});
    }

    function getCustomerExperience(agent) {
        let customerExperience = agent.query;
        db.collection("users").add({customerExperience: customerExperience});
    }
  
    let intentMap = new Map();
    intentMap.set('age', age);
    intentMap.set('location', getLocation);
    intentMap.set('customer-experience', getCustomerExperience);
    agent.handleRequest(intentMap);
});

但数据存储在不同的文档 ID 中:

存储在不同 ID 中的数据

我想要实现的是这样的:

存储在同一 ID 中的数据

如果我不清楚,请告诉我。我是 Dialogflow、Firebase 以及 JS 语言的新手。干杯!

标签: google-cloud-firestoregoogle-cloud-functionsdialogflow-esdialogflow-es-fulfillment

解决方案


你在正确的轨道上!您的原始代码的根本问题是collection.add()创建一个新文档。但是您有时希望它创建一个新文档,并在其他时候将其保存在以前的文档中。

这意味着,在整个 Dialogflow 会话期间,您需要通过某种方式来了解文档名称是什么或应该是什么。有几种可能的方法可以做到这一点。

使用基于会话的文档

Dialogflow 提供了一个会话标识符,您可以agent.session使用 dialogflow-fulfillment 库将其作为属性的一部分获取,session如果您直接解析JSON 请求正文,则可以在属性中获取该标识符。

但是,此字符串包含正斜杠/字符,应避免在文档名称中使用。幸运的是,该字符串的格式被记录为以下两种格式之一:

  • projects/项目 ID /agent/sessions/会话 ID
  • projects/项目 ID /agent/environments/环境 ID /users/用户 ID /sessions/会话 ID

在每种情况下,会话 ID都是此路径的最后一部分,因此您可能可以使用类似这样的代码来获取会话 ID,将其用作文档名称,然后保存一个属性(例如,年龄)为了它:

  function documentRef( agent ){
    const elements = agent.session.split('/');
    const lastElement = elements[elements.length - 1];
    return db.collection('users').doc(lastElement);
  }

  async function getCourier(agent) {
    const ref = documentRef( agent );
    const age = agent.parameters.age;
    return await ref.update({age: age});
  }

请注意,我还创建了getCourier()一个异步函数,因为更改数据库的函数调用(例如ref.update())是异步函数,而 Dialogflow 要求您将其设为异步函数或显式返回 Promise。如果你想返回一个 Promise ,这更像是这样的:

  function getCourier(agent) {
    const ref = documentRef( agent );
    const age = agent.parameters.age;
    return ref.update({age: age});
  }

使用 Firestore 生成的文档名称

使用此方法,您可以将文档名称存储为 Context 参数。当您去保存一个值时,您将检查是否设置了此文档名称。如果是,您将update()使用此文档名称。如果没有,您将执行一个add(),获取文档名称,并将其保存在 Context 参数中。

它可能看起来像这样(未经测试),同样适用于这个时代:

  async function getCourier( agent ){
    const ref = db.collection('users');
    const age = agent.parameters.age;

    const docContext = agent.context.get('doc');
    if( !docContext ){
      // We don't previously have a document, so create it
      const res = await ref.add({age:age});
      // And set a long-lasting context with a "name" parameter
      // containing the document id
      agent.context.set( 'doc', 99, {
        'name': ref.id
      } );
      
    } else {
      // There is a context with the document name already set
      // so get the name
      const docName = docContext.parameters['name'];
      const docRef = ref.doc(docName);
      // And save the data at this location
      await docRef.update({age: age});
    }
  }

同样,这使用了异步函数。如果你更愿意使用 Promise,它可能更像这样:

  function getCourier( agent ){
    const ref = db.collection('users');
    const age = agent.parameters.age;

    const docContext = agent.context.get('doc');
    if( !docContext ){
      // We don't previously have a document, so create it
      return ref.add({age:age})
        .then( ref => {
          // And set a long-lasting context with a "name" parameter
          // containing the document id
          agent.context.set( 'doc', 99, {
            'name': ref.id
          } );
        });
      
    } else {
      // There is a context with the document name already set
      // so get the name
      const docName = docContext.parameters['name'];
      const docRef = ref.doc(docName);
      // And save the data at this location
      return docRef.update({age: age});
    }
  }

使用您在上下文中生成并保存的文档名称

您不需要使用第一个替代方案中的会话 ID。如果您有一些对您自己有意义的 ID 或名称(例如,用户名或时间戳,或某种组合),那么您可以将其保存在 Context 参数中,并每次都将其用作文档名称。这是上述第一种和第二种方法的组合(但可能比第二种方法更简单,因为您不需要在第一次创建文档时获取文档名称)。


推荐阅读