Невозможно записать информацию о состоянии пользователя в расшифровку - PullRequest
1 голос
/ 09 июля 2020

Я использую TranscriptLoggerMiddleware и CosmosDB для регистрации транскриптов моего чат-бота. Мы пытаемся зафиксировать информацию о состоянии пользователя (имя пользователя, номер учетной записи, тип учетной записи и т. Д. c) в качестве атрибутов верхнего уровня в транскрипте, чтобы указанные c клиенты могли легко запрашиваться в базе данных (если эта информация только в отдельных атрибутах временной метки документа их нельзя запросить).

В идеале я бы просто добавил состояние пользователя при создании файла, но я не могу найти способ получить доступ это, поскольку регистратор определен в index. js, а TranscriptLoggerMiddleware предоставляет только activity моей функции, а не полный context. Если у кого-то есть способ получить данные о состоянии пользователя через TranscriptLoggerMiddleware, дайте мне знать, это решит эту проблему. Вот код customLogger. Обратите внимание, что из-за того, что функция получает как пользовательский запрос, так и ответ бота, я не смог получить и повторно сохранить стенограмму для работы, поэтому я перезаписываю стенограмму из локального объекта журнала. Не пытаюсь придумать здесь новый подход, но если кто-то решит общую проблему, я бы хотел это услышать.

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

const { CosmosDbPartitionedStorage } = require('botbuilder-azure');
const path = require('path');

/**
 * CustomLogger, takes in an activity and saves it for the duration of the conversation, writing to an emulator compatible transcript file in the transcriptsPath folder.
 */
class CustomLogger {
    /**
     * Log an activity to the log file.
     * @param activity Activity being logged.
     */
            
    // Set up Cosmos Storage
    constructor(appInsightsClient) {
        this.transcriptStorage = new CosmosDbPartitionedStorage({
            cosmosDbEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
            authKey: process.env.COSMOS_AUTH_KEY,
            databaseId: process.env.DATABASE,
            containerId: 'bot-transcripts'
        });
           
        this.conversationLogger = {};

        this.appInsightsClient = appInsightsClient;

        this.msDelay = 250;
    }
        
        
    async logActivity(activity) {
        
        if (!activity) {
            throw new Error('Activity is required.');
        }
        
        // Log only if this is type message
        if (activity.type === 'message') {
            
            if (activity.attachments) {
                try {
                    var logTextDb = `${activity.from.name}: ${activity.attachments[0].content.text}`;
                } catch (err) {
                    var logTextDb = `${activity.from.name}: ${activity.text}`;
                }
            } else {
                var logTextDb = `${activity.from.name}: ${activity.text}`;
            }
            
            if (activity.conversation) {
                var id = activity.conversation.id;
                if (id.indexOf('|') !== -1) {
                    id = activity.conversation.id.replace(/\|.*/, '');
                }
                      
                // Get today's date for datestamp
                var currentDate = new Date();
                var day = currentDate.getDate();
                var month = currentDate.getMonth()+1;
                var year = currentDate.getFullYear();
                var datestamp = year + '-' + month + '-' + day;
                var fileName = `${datestamp}_${id}`;
        
                var timestamp = Math.floor(Date.now()/1);
                
                // CosmosDB logging (JK)
                if (!(fileName in this.conversationLogger)) {
                    this.conversationLogger[fileName] = {};
                    this.conversationLogger[fileName]['userData'] = {};
                    this.conversationLogger[fileName]['botName'] = process.env.BOTNAME;
                }
            
                this.conversationLogger[fileName][timestamp] = logTextDb;
            
                let updateObj = {
                
                    [fileName]:{
                        ...this.conversationLogger[fileName]
                    }
                
                }
            
                // Add delay to ensure messages logged sequentially
                await this.wait(this.msDelay);
            
                try {
                    let result = await this.transcriptStorage.write(updateObj);
                } catch(err) {
                    console.log(err);
                    this.appInsightsClient.trackTrace({message: `Logger Error ${err.code} - ${path.basename(__filename)}`,severity: 3,properties: {'botName': process.env.BOTNAME, 'error':err.body}});
                }
            }
        }
    }
    async wait(milliseconds) {
        var start = new Date().getTime();
        for (var i = 0; i < 1e7; i++) {
            if ((new Date().getTime() - start) > milliseconds) {
                break;
            }
        }
    }
}
exports.CustomLogger = CustomLogger;

Не имея возможности получить состояние пользователя в этой функции, я решил попробовать несколько других подходов. Самым многообещающим было создание отдельной функции updateTranscript для захвата транскрипта, добавления состояния пользователя и его сохранения. Но я думаю, что он ловил его только по запросу пользователя и снова перекрывался локальным объектом при ответе бота. Я добавил задержку, чтобы попытаться бороться с этим, но это все равно не сработало. В моем самом первом запросе о предоставлении номера клиента данные о состоянии пользователя сохраняются в расшифровке, но при следующем действии они исчезают и никогда не возвращаются (хотя я вижу, что они предположительно записываются в БД). Вот эта функция обновления.

const { CosmosDbStorage } = require('botbuilder-azure');

var updateTranscript = async (context, userData, appInsightsClient) => {
    const transcriptStorage = new CosmosDbStorage({
        serviceEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
        authKey: process.env.COSMOS_AUTH_KEY,
        databaseId: process.env.DATABASE,
        collectionId: 'bot-transcripts',
        partitionKey: process.env.BOTNAME
    });

    var id = context.activity.conversation.id;
    if (id.indexOf('|') !== -1) {
        id = context.activity.conversation.id.replace(/\|.*/, '');
    }

    // Get today's date for datestamp
    var currentDate = new Date();
    var day = currentDate.getDate();
    var month = currentDate.getMonth()+1;
    var year = currentDate.getFullYear();
    var datestamp = year + '-' + month + '-' + day;
    var filename = `${datestamp}_${id}`;

    var msDelay = 500;
    await new Promise(resolve => setTimeout(resolve, msDelay));
    
    var transcript = await transcriptStorage.read([filename]);

    transcript[filename]['userData'] = userData

    try {
        await transcriptStorage.write(transcript);
        console.log('User data added to transcript');
    } catch(err) {
        console.log(err);
        appInsightsClient.trackTrace({message: `Log Updater Error ${err.code} - ${path.basename(__filename)}`,severity: 3,properties: {'botName': process.env.BOTNAME, 'error':err.body}});
    }

    return;
}

module.exports.updateTranscript = updateTranscript

Я понимаю, что этот подход является чем-то вроде кластера, но мне не удалось найти ничего лучше. Я знаю, что у бота Microsoft COVID-19 есть действительно хорошая функция извлечения стенограммы, но я не смог получить от них какую-либо информацию о том, как это было выполнено. Кроме того, я очень рад продолжить эту реализацию, если кто-то может помочь мне выяснить, как внести это состояние пользователя в стенограмму без перезаписи или возникновения проблем с параллелизмом.

Что касается того, почему я могу '' t запросить номер счета даже с помощью функции substring(), вот пример объекта данных документа. Я понятия не имею, в какой строке проверять наличие подстроки, в данном случае 122809. Я не знаю, что это за метка времени. Если это хранится на верхнем уровне (например, userData / accountNumber), я точно знаю, где искать значение. Для дальнейшего контекста я показал то, что вижу после первого запроса на номер учетной записи, где указаны userData. Но при последующих записях он перекрывается, и я не могу вернуть его даже с задержкой в ​​моей функции updateTranscript.

"document": {
        "userData": {},
        "botName": "AveryCreek_OEM_CSC_Bot_QA",
        "1594745997562": "AveryCreek_OEM_CSC_Bot_QA: Hi! I'm the OEM CSC Support Bot! Before we get started, can you please provide me with your 6-digit Vista number? If you don't have one, just type \"Skip\".",
        "1594746003973": "You: 122809",
        "1594746004241": "AveryCreek_OEM_CSC_Bot_QA: Thank you. What can I help you with today? \r\nYou can say **Menu** for a list of common commands, **Help** for chatbot tips, or choose one of the frequent actions below.  \r\n  \r\n  I'm still being tested, so please use our [Feedback Form](https://forms.office.com/Pages/ResponsePage.aspx?id=lVxS1ga5GkO5Jum1G6Q8xHnUJxcBMMdAqVUeyOmrhgBUNFI3VEhMU1laV1YwMUdFTkhYVzcwWk9DMiQlQCN0PWcu) to let us know how well I'm doing and how I can be improved!",
        "1594746011384": "You: what is my account number?",
        "1594746011652": "AveryCreek_OEM_CSC_Bot_QA: Here is the informaiton I have stored: \n   \n**Account Number:** 122809 \n\n I will forget everything except your account number after the end of this conversation.",
        "1594746011920": "AveryCreek_OEM_CSC_Bot_QA: 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?",
        "1594746016034": "You: no",
        "1594746016301": "AveryCreek_OEM_CSC_Bot_QA: OK, I won't clear your information. You can ask again at any time."
    },

"document": {
        "userData": {
            "accountNumber": "122809"
        },
        "botName": "AveryCreek_OEM_CSC_Bot_QA",
        "1594746019952": "AveryCreek_OEM_CSC_Bot_QA: Hi! I'm the OEM CSC Support Bot! What can I help you with today? \r\nYou can say **Menu** for a list of common commands, **Help** for chatbot tips, or choose one of the frequent actions below.  \r\n  \r\n  I'm still being tested, so please use our [Feedback Form](https://forms.office.com/Pages/ResponsePage.aspx?id=lVxS1ga5GkO5Jum1G6Q8xHnUJxcBMMdAqVUeyOmrhgBUNFI3VEhMU1laV1YwMUdFTkhYVzcwWk9DMiQlQCN0PWcu) to let us know how well I'm doing and how I can be improved!"
    },

1 Ответ

1 голос
/ 17 июля 2020

Вы сказали, что столкнулись с проблемами параллелизма, хотя JavaScript однопоточный. Как бы странно это ни звучало, я думаю, что в каком-то смысле вы правы. TranscriptLoggerMiddleware действительно имеет свой собственный буфер, который он использует для хранения действий на протяжении хода, а затем пытается зарегистрировать их все сразу. Он мог легко предоставить способ получить весь этот буфер в вашей собственной функции регистратора, но вместо этого он просто просматривает буфер, так что вы все равно можете регистрировать их только по отдельности. Кроме того, он позволяет logActivity возвращать обещание, но никогда не ожидает его, поэтому каждое действие будет регистрироваться «одновременно» (на самом деле это не совсем одновременно, но код, скорее всего, будет перескакивать между вызовами функций, прежде чем ждать их завершения). Это проблема для любой операции, отличной от atomi c, потому что вы будете изменять состояние, не зная о его последних модификациях.

while (transcript.length > 0) {
    try {
        const activity: Activity = transcript.shift();
        // If the implementation of this.logger.logActivity() is asynchronous, we don't
        // await it as to not block processing of activities.
        // Because TranscriptLogger.logActivity() returns void or Promise<void>, we capture
        // the result and see if it is a Promise.
        const logActivityResult = this.logger.logActivity(activity);

        // If this.logger.logActivity() returns a Promise, a catch is added in case there
        // is no innate error handling in the method. This catch prevents
        // UnhandledPromiseRejectionWarnings from being thrown and prints the error to the
        // console.
        if (logActivityResult instanceof Promise) {
            logActivityResult.catch(err => {
                this.transcriptLoggerErrorHandler(err);
            });
        }
    } catch (err) {
        this.transcriptLoggerErrorHandler(err);
    }
}

В общем, я не думайте, что промежуточное программное обеспечение регистратора транскриптов - это путь к go здесь. Хотя он может служить вашим целям, с ним слишком много проблем. Я бы либо написал свое собственное промежуточное программное обеспечение, либо просто поместил бы код промежуточного программного обеспечения непосредственно в свой лог-файл бота c вот так:

async onTurn(turnContext) {
    const activity = turnContext.activity;
    
    await this.logActivity(turnContext, activity);
    
    turnContext.onSendActivities(async (ctx, activities, next) => {
        for (const activity of activities) {
            await this.logActivity(ctx, activity);
        }
        
        return await next();
    });

    // Bot code here

    // Save state changes
    await this.userState.saveChanges(turnContext);
}

async logActivity(turnContext, activity) {
    var transcript = await this.transcriptProperty.get(turnContext, []);
    transcript.push(activity);
    await this.transcriptProperty.set(turnContext, transcript);
    console.log('Activities saved: ' + transcript.length);
}

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

...