Botframework V4: конфигурация Cosmos DB - PullRequest
0 голосов
/ 15 февраля 2019

Здравствуйте, мне трудно сконфигурировать мою базу данных cosmos для botframework. До этого при использовании памяти она работала нормально.Я читаю это и это в качестве руководства.Я включил ошибки с комментариями в кодах.Кто-нибудь может мне с этим помочь.Я был бы очень признателен за помощь.Я исследую это уже 3 дня.Спасибо!

 public class Startup
{
    private const string CosmosServiceEndpoint = "xxxxxxxxxxx";
    private const string CosmosDBKey = "xxxxxxxxxxx";
    private const string CosmosDBDatabaseName = "xxxxxxxxxxx";
    private const string CosmosDBCollectionNameConState = "conversationState";
    private const string CosmosDBCollectionNameUserState = "userState";

    private ILoggerFactory _loggerFactory;
    private bool _isProduction = false;

    public Startup(IHostingEnvironment env)
    {
        _isProduction = env.IsProduction();

        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();

        Configuration = builder.Build();
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddBot<BasicBot>(options =>
        {
            ILogger logger = _loggerFactory.CreateLogger<BasicBot>();

            var secretKey = Configuration.GetSection("botFileSecret")?.Value;
            var botFilePath = Configuration.GetSection("botFilePath")?.Value;
            if (!File.Exists(botFilePath))
            {
                throw new FileNotFoundException($"The .bot configuration file was not found. botFilePath: {botFilePath}");
            }

            BotConfiguration botConfig = null;
            try
            {
                botConfig = BotConfiguration.Load(botFilePath ?? @".\echo-with-counter.bot", secretKey);
            }
            catch
            {
                var msg = @"Error reading bot file. Please ensure you have valid botFilePath and botFileSecret set for your environment.
                - You can find the botFilePath and botFileSecret in the Azure App Service application settings.
                - If you are running this bot locally, consider adding a appsettings.json file with botFilePath and botFileSecret.
                - See https://aka.ms/about-bot-file to learn more about .bot file its use and bot configuration.
                ";
                logger.LogError(msg);
                throw new InvalidOperationException(msg);
            }

            services.AddSingleton(sp => botConfig);

            var environment = _isProduction ? "production" : "development";
            var service = botConfig.Services.FirstOrDefault(s => s.Type == "endpoint" && s.Name == environment);
            if (service == null && _isProduction)
            {
                service = botConfig.Services.Where(s => s.Type == "endpoint" && s.Name == "development").FirstOrDefault();
                logger.LogWarning("Attempting to load development endpoint in production environment.");
            }

            if (!(service is EndpointService endpointService))
            {
                throw new InvalidOperationException($"The .bot file does not contain an endpoint with name '{environment}'.");
            }

            options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);

            options.OnTurnError = async (context, exception) =>
            {
                logger.LogError($"Exception caught : {exception}");
                await context.SendActivityAsync("Sorry, it looks like something went wrong.");
            };

            // The Memory Storage used here is for local bot debugging only. When the bot
            // is restarted, everything stored in memory will be gone.
            // IStorage dataStore = new MemoryStorage();

           // error : COSMOSDBSTORAGE DOES NOT CONTAIN CONSTRUCTOR TAKES 4 ARGUMENTS
           //IStorage dataStoreConversationState =
            // new CosmosDbStorage(
            //     uri,
            //     "** auth key **",
            //     "helloworldbot",
            //     "conversationstate");

            var uri = new Uri(CosmosServiceEndpoint);

            IStorage dataStoreConversationState =
            new CosmosDbStorage(new CosmosDbStorageOptions
            {
                AuthKey = CosmosDBKey,
                CollectionId = CosmosDBCollectionNameConState,
                CosmosDBEndpoint = new Uri(CosmosServiceEndpoint),
                DatabaseId = CosmosDBDatabaseName,
            });

            IStorage dataStoreUserState =
            new CosmosDbStorage(new CosmosDbStorageOptions
            {
                AuthKey = CosmosDBKey,
                CollectionId = CosmosDBCollectionNameUserState,
                CosmosDBEndpoint = new Uri(CosmosServiceEndpoint),
                DatabaseId = CosmosDBDatabaseName,
            });

           //error : THE NON GENERIC TYPE "CONVERSATIONsTATE" CANNOT BE USED WITH TYPED ARGUMENTS
            options.Middleware.Add(new ConversationState<BasicState>(dataStoreConversationState));
            options.Middleware.Add(new UserState<BasicUserState>(dataStoreUserState));

    }

Ответы [ 2 ]

0 голосов
/ 16 февраля 2019

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

Таким образом, вы больше не создаете ConversationState<T> для каждой части состояния, которую хотите поддерживать.Вместо этого вы создаете один ConversationState, который, если хотите, действует как своего рода «область действия», а затем вы создаете много свойств в этой «области», используя CreateProperty<T> API.

Вот так:

var conversationState = new ConversationState(myStorage);

var myBasicStateProperty conversationState.CreateProperty<BasicState>("MyBasicState");

Теперь, как я уже сказал, это больше не промежуточное ПО.Вместо этого, то, что возвращается из CreateProperty<T>, это IStatePropertyAccessor<T>, который вы можете затем передать во все, что нужно для его использования (например, в своего бота).Точно так же вы также передадите сам ConversationState в бота, чтобы он мог в конечном итоге вызвать SaveChangesAsync в конце хода.В качестве альтернативы вы можете настроить AutoSaveStateMiddleware, который позаботится о сохранении состояния для вас в конце каждого хода, но вы потеряете контроль над способностью обрабатывать исключения, возникающие во время вызова SaveChangesAsync (например, сетевой раздел,параллелизм данных и т. д.).

0 голосов
/ 16 февраля 2019

Вероятность того, что это не работает для вас, высока, потому что в обеих этих ссылках указано, что вам нужно создать новую коллекцию в вашем ресурсе CosmosDB в Azure.Microsoft недавно обновила ресурс CosmosDB, требуя, чтобы новые коллекции создавались с ключами разделов, которые еще не поддерживаются в Bot Framework.В настоящее время есть Запрос на изменение дизайна , чтобы добавить эту возможность, но он остановлен C # Cosmos SDK.

А пока начните с создания ресурса Cosmos в Azure и НЕ делайтебаза данных или коллекция.ТОЛЬКО сделайте ресурс Космос.SDK фреймворка ботов настроен на создание новой БД и коллекции, если указанная вами не существует, и она может создать ее без разделов ... так что пусть бот сделает всю работу здесь.

Iиспользовал вторую ссылку, которую вы опубликовали , чтобы изменить образец бота Simple Prompt для работы с Cosmos.Примечание: endpoint и key являются значениями по умолчанию для эмулятора CosmosDB , который вы можете использовать для локального тестирования, если хотите.

Вот мой startup.cs:

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

using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Azure;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Integration;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Configuration;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.BotBuilderSamples
{
    /// <summary>
    /// The Startup class configures services and the app's request pipeline.
    /// </summary>
    public class Startup
    {
        private const string CosmosServiceEndpoint = "https://localhost:8081";
        private const string CosmosDBKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
        private const string CosmosDBDatabaseName = "bot-cosmos-sql-db";
        private const string CosmosDBCollectionName = "bot-storage";

        private static readonly CosmosDbStorage _myStorage = new CosmosDbStorage(new CosmosDbStorageOptions
        {
            AuthKey = CosmosDBKey,
            CollectionId = CosmosDBCollectionName,
            CosmosDBEndpoint = new Uri(CosmosServiceEndpoint),
            DatabaseId = CosmosDBDatabaseName,
        });

        private ILoggerFactory _loggerFactory;
        private bool _isProduction = false;

        public Startup(IHostingEnvironment env)
        {
            _isProduction = env.IsProduction();

            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();

            Configuration = builder.Build();
        }

        /// <summary>
        /// Gets the configuration that represents a set of key/value application configuration properties.
        /// </summary>
        /// <value>
        /// The <see cref="IConfiguration"/> that represents a set of key/value application configuration properties.
        /// </value>
        public IConfiguration Configuration { get; }

        /// <summary>
        /// This method gets called by the runtime. Use this method to add services to the container.
        /// </summary>
        /// <param name="services">The <see cref="IServiceCollection"/> specifies the contract for a collection of service descriptors.</param>
        /// <seealso cref="IStatePropertyAccessor{T}"/>
        /// <seealso cref="https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection"/>
        /// <seealso cref="https://docs.microsoft.com/en-us/azure/bot-service/bot-service-manage-channels?view=azure-bot-service-4.0"/>
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddBot<SimplePromptBot>(options =>
            {
                var secretKey = Configuration.GetSection("botFileSecret")?.Value;
                var botFilePath = Configuration.GetSection("botFilePath")?.Value;
                if (!File.Exists(botFilePath))
                {
                    throw new FileNotFoundException($"The .bot configuration file was not found. botFilePath: {botFilePath}");
                }

                // Loads .bot configuration file and adds a singleton that your Bot can access through dependency injection.
                var botConfig = BotConfiguration.Load(botFilePath ?? @".\simple-prompt.bot", secretKey);
                services.AddSingleton(sp => botConfig ?? throw new InvalidOperationException($"The .bot configuration file could not be loaded. botFilePath: {botFilePath}"));

                // Retrieve current endpoint.
                var environment = _isProduction ? "production" : "development";
                var service = botConfig.Services.FirstOrDefault(s => s.Type == "endpoint" && s.Name == environment);
                if (!(service is EndpointService endpointService))
                {
                    throw new InvalidOperationException($"The .bot file does not contain an endpoint with name '{environment}'.");
                }

                options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);

                // Creates a logger for the application to use.
                ILogger logger = _loggerFactory.CreateLogger<SimplePromptBot>();

                // Catches any errors that occur during a conversation turn and logs them.
                options.OnTurnError = async (context, exception) =>
                {
                    logger.LogError($"Exception caught : {exception}");
                    await context.SendActivityAsync("Sorry, it looks like something went wrong.");
                };

                // Memory Storage is for local bot debugging only. When the bot
                // is restarted, everything stored in memory will be gone.
                //IStorage dataStore = new MemoryStorage();

                // For production bots use the Azure Blob or
                // Azure CosmosDB storage providers. For the Azure
                // based storage providers, add the Microsoft.Bot.Builder.Azure
                // Nuget package to your solution. That package is found at:
                // https://www.nuget.org/packages/Microsoft.Bot.Builder.Azure/
                // Uncomment the following lines to use Azure Blob Storage
                // //Storage configuration name or ID from the .bot file.
                // const string StorageConfigurationId = "<STORAGE-NAME-OR-ID-FROM-BOT-FILE>";
                // var blobConfig = botConfig.FindServiceByNameOrId(StorageConfigurationId);
                // if (!(blobConfig is BlobStorageService blobStorageConfig))
                // {
                //    throw new InvalidOperationException($"The .bot file does not contain an blob storage with name '{StorageConfigurationId}'.");
                // }
                // // Default container name.
                // const string DefaultBotContainer = "<DEFAULT-CONTAINER>";
                // var storageContainer = string.IsNullOrWhiteSpace(blobStorageConfig.Container) ? DefaultBotContainer : blobStorageConfig.Container;
                // IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureBlobStorage(blobStorageConfig.ConnectionString, storageContainer);

                // Create Conversation State object.
                // The Conversation State object is where we persist anything at the conversation-scope.
                var conversationState = new ConversationState(_myStorage);

                options.State.Add(conversationState);
            });

            services.AddSingleton(sp =>
            {
                // We need to grab the conversationState we added on the options in the previous step.
                var options = sp.GetRequiredService<IOptions<BotFrameworkOptions>>().Value;
                if (options == null)
                {
                    throw new InvalidOperationException("BotFrameworkOptions must be configured prior to setting up the State Accessors");
                }

                var conversationState = options.State.OfType<ConversationState>().FirstOrDefault();
                if (conversationState == null)
                {
                    throw new InvalidOperationException("ConversationState must be defined and added before adding conversation-scoped state accessors.");
                }

                // The dialogs will need a state store accessor. Creating it here once (on-demand) allows the dependency injection
                // to hand it to our IBot class that is create per-request.
                var accessors = new SimplePromptBotAccessors(conversationState)
                {
                    ConversationDialogState = conversationState.CreateProperty<DialogState>("DialogState"),
                };

                return accessors;
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            _loggerFactory = loggerFactory;

            app.UseDefaultFiles()
                .UseStaticFiles()
                .UseBotFramework();
        }
    }
}

Вот разница , так что вы можете легко увидеть различия кода.

Вот скриншотэто работает:

enter image description here

Похоже, ваш код также хранит userState и разговорState в отдельных коллекциях.Я думаю, что это работает ... но "обычным" методом является создание только одного экземпляра CosmosDbStorage.Бот будет хранить userState и разговорState в отдельных документах в коллекции.Обратите внимание, что в дополнение к приведенному выше коду вам, вероятно, понадобится что-то вроде var userState = new UserState(_myStorage), поскольку ваш код также использует userState, а вышеприведенный код - нет.

Кроме того, и в соответствии с ответом Дрю, ядумаю, что код из того учебника, который вы связали , может вызывать некоторые проблемы, просто потому что он устарел.Лучше всего было бы найти соответствующий образец из GitHub Repo и использовать его в качестве руководства. Базовый бот - хороший, с функциональностью chatState и userState.

...