【问题标题】:Bot Framework V4 MS teams channel and GDPRBot Framework V4 MS 团队渠道和 GDPR
【发布时间】:2021-03-11 19:06:40
【问题描述】:

使用 Teams 聊天机器人(V4/节点)并需要解决 GDPR。

简而言之,聊天机器人的用户需要能够导出或删除聊天机器人存储的个人数据。个人数据是与已识别或可识别的自然人相关的任何信息。状态对象中的用户 ID 也是如此。

我阅读了一篇关于 GDPR and bots 的博客,但这篇博客并未涉及 Teams 频道。而且是关于V3

  1. 用户在对话框中提供的个人数据(由我编写)是 容易的部分。我会写一些对话框来显示和删除它们 (就像比尔在他的回答中所做的那样)。
  2. 实际对话中的内容是 Teams 平台的一部分,\应该在 Teams 本身中处理。

我不知道如何处理的是机器人实际运行的数据(机器人状态等)。如果用户需要删除他或她参与某个对话的事实怎么办。这可能存储在某些状态对象中(在我的情况下是 Blob 存储)。但是哪些?

我将不胜感激有关如何解决此问题的一些想法\指导。

【问题讨论】:

    标签: botframework microsoft-teams azure-bot-service


    【解决方案1】:

    免责声明:我不是 GDPR 专家,但我认为以下内容就足够了。

    从机器人的角度来看,存储在 Teams 频道中的数据是相同的。您拥有通常(在大多数示例中)使用 Blob 存储设置的对话状态和用户状态数据。我对这些项目使用 conversationStateuserState 命名法。

    在我的用例中,我将帐号存储在 userState 中,将用户名/电子邮件存储在 conversationState 中。请注意,机器人存储的其他内容(尤其是在conversationState 我相信)围绕对话状态和其他机器人特定的内容通常相当无意义,但我不知道它们是否会被视为 GDPR 的一部分。无论如何,我们都会清除这些全部对象。

    为此,我创建了一个对话框来管理用户配置文件,该配置文件显示存储的关键信息(我专门访问帐号、用户名和电子邮件),然后提示用户是否要删除信息.在 nodejs 中是这样的。

    const { ConfirmPrompt, ComponentDialog, WaterfallDialog } = require('botbuilder-dialogs');
    const { ActivityTypes } = require('botbuilder');
    
    const WATERFALL_DIALOG = 'waterfallDialog';
    const CONFIRM_PROMPT = 'confirmPrompt';
    
    class manageProfileDialog extends ComponentDialog {
        constructor(dialogId, userDialogStateAccessor, userState, appInsightsClient, dialogState, conversationState) {
            super(dialogId);
    
            this.dialogs.add(new ConfirmPrompt(CONFIRM_PROMPT));
            this.dialogs.add(new WaterfallDialog(WATERFALL_DIALOG, [
                this.showInfoAndPrompt.bind(this),
                this.confirmDelete.bind(this)
            ]));
    
            this.initialDialogId = WATERFALL_DIALOG;
    
            // State accessors
            this.userDialogStateAccessor = userDialogStateAccessor;
            this.userState = userState;
            this.dialogState = dialogState;
            this.conversationState = conversationState;
    
            this.appInsightsClient = appInsightsClient;
    
        } // End constructor
    
        async showInfoAndPrompt(step) {
            this.appInsightsClient.trackEvent({name:'manageProfileDialog', properties:{instanceId:step._info.values.instanceId, channel: step.context.activity.channelId}});
            this.appInsightsClient.trackMetric({name: 'showInfoAndPrompt', value: 1});
    
            const userProfile = await this.userDialogStateAccessor.get(step.context, {});
            const conversationData = await this.dialogState.get(step.context, {});
    
            if (!userProfile.accountNumber & !conversationData.userEmail & !conversationData.userFullName & !conversationData.orderType) {
                this.appInsightsClient.trackEvent({name:'manageProfileDialogEnd', properties:{instanceId:step._info.values.instanceId, channel: step.context.activity.channelId}});
                this.appInsightsClient.trackMetric({name: 'confirmDelete', value: 1});
    
                await step.context.sendActivity(`I don't have any of your information stored.`);
                return await step.endDialog();
            } else {
                var storedData = '';
                if (userProfile.accountNumber) {
                    storedData += `  \n**Account Number:** ${userProfile.accountNumber}`;
                }
                if (conversationData.userFullName) {
                    storedData += `  \n**Name:** ${conversationData.userFullName}`;
                }
                if (conversationData.userEmail) {
                    storedData += `  \n**Email:** ${conversationData.userEmail}`;
                }
                if (conversationData.orderType) {
                    storedData += `  \n**Default order type:** ${conversationData.orderType}`;
                }
                await step.context.sendActivity(`Here is the informaiton I have stored: \n ${storedData} \n\n I will forget everything except your account number after the end of this conversation.`);
                await step.context.sendActivity({ type: ActivityTypes.Typing });
                await new Promise(resolve => setTimeout(resolve, process.env.DIALOG_DELAY));
                return await step.prompt(CONFIRM_PROMPT, `I can clear your information if you don't want me to store it or if you want to reneter it. Would you like me to clear your information now?`,['Yes','No']);
            }
        }
    
        async confirmDelete(step) {
            this.appInsightsClient.trackEvent({name:'manageProfileDialogEnd', properties:{instanceId:step._info.values.instanceId, channel: step.context.activity.channelId}});
            if (step.result) {
                const userProfile = await this.userDialogStateAccessor.delete(step.context, {});
                const conversationData = await this.dialogState.delete(step.context, {});
                await step.context.sendActivity(`OK, I have cleared your information.`);
                return await step.endDialog();
            } else {
                await step.context.sendActivity(`OK, I won't clear your information. You can ask again at any time.`);
                this.appInsightsClient.trackMetric({name: 'confirmDelete', value: 1});
                return await step.endDialog();
            }
        }
    
    }
    
    module.exports.ManageProfileDialog = manageProfileDialog;
    

    关于 GDPR,我不确定的一件事是,您是否在运行机器人的过程中将成绩单或活动数据存储在其他地方。例如,我将对话记录存储在 CosmosDB 中,其中可能包括姓名和电子邮件地址等内容(如果在对话过程中提供的话)。即使我想,我也没有清除这些信息的好方法。此外,我将 LUIS 跟踪和其他信息存储在 Application Insights 中,在许多情况下,这些信息包括可能附加了用户名或 ID 等内容的活动。我什至不确定是否可以从 Application Insights 中删除这些跟踪。我不知道这些是否属于 GDPR 的范围,因为它们是可操作的,但如果这是一个潜在的问题,请注意您在日志记录和/或脚本应用程序中存储的内容。

    【讨论】:

    • 谢谢比尔。我不关心机器人在对话期间请求的数据。我知道这一点并且应该能够管理它(就像您在代码中所做的那样)我担心数据存储在我不知道的后台。通过机器人服务,团队可以引导等根据 GDPR 被视为个人数据的数据。例如人员团队 ID。
    • 顺便说一句:你的意思是什么:“不管我们将把这些整个物体都抹掉。”
    • 我的意思是.delete 方法将删除整个上下文对象,而不仅仅是我明确保存到这些对象的数据。 Bot State Service 不再存在,因此应该存储以供 bot 使用的所有内容都应该在这些用户和对话状态对象中,尽管您可能希望 MS 中的某个人确认绝对确定。
    • tnx. userProfile = await this.userDialogStateAccessor.delete(step.context, {}) 中的空对象引发错误。我使用了 await this.userState.delete(stepContext.context) ,它实际上删除了整个状态对象(也从存储中删除)。为我工作。
    • 太棒了。我实际上不确定为什么我第一次使用的示例将空对象分配给一个变量,但它对我有用,所以我没有改变它!只是等待 .delete 操作和不将其分配给变量应该没有区别。
    猜你喜欢
    • 1970-01-01
    • 2020-05-14
    • 1970-01-01
    • 2018-01-27
    • 1970-01-01
    • 2019-03-26
    • 2021-01-27
    • 1970-01-01
    • 2019-02-25
    相关资源
    最近更新 更多