Dialogflow to Firestore using Inline Fulfillment - 将所有用户数据存储在一个文档中

Dialogflow to Firestore using Inline Fulfillment - Store all user data in one document

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

我试过这段代码:

'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 中:

我想要实现的是这样的:

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

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

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

使用基于会话的文档

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

但是,此字符串包含正斜杠 / 个字符,应 avoided in document names. Fortunately, the format of this string is documented 为以下两种格式之一:

  • 项目/项目 ID/agent/sessions/会话 ID
  • 项目/项目 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 要求您将其设为异步函数或显式 return 一个 Promise。如果你希望 return 一个 Promise,这将更像这样:

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

使用Firestore生成的文件名

使用此方法,您可以将文档名称存储为上下文参数。当你去保存一个值时,你会检查是否设置了这个文档名称。如果是,您将使用此文档名称执行 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 参数中,并每次都将其用作文档名称。这是上面第一种和第二种方法的组合(但可能比第二种方法更简单,因为您不需要在第一次创建文档时获取文档名称)。