google-cloud-firestore - 使用 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 中:
我想要实现的是这样的:
如果我不清楚,请告诉我。我是 Dialogflow、Firebase 以及 JS 语言的新手。干杯!
解决方案
你在正确的轨道上!您的原始代码的根本问题是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 参数中,并每次都将其用作文档名称。这是上述第一种和第二种方法的组合(但可能比第二种方法更简单,因为您不需要在第一次创建文档时获取文档名称)。