跳到主要內容

[Bot Framework] 應用微軟Cognitive Services實作LUIS Bot (III), 實作LUIS Bot



首先,

建立一個Score.cs來表達分數

    public class Score
    {
        public Score(string user, int point, DateTime dateTime)
        {
            User = user;
            Point = point;
            DateTime = dateTime;
        }
        public string User { get; set; }
        public int Point { get; set; }
        public DateTime DateTime { get; set; }
        public override string ToString()
        {
            StringBuilder sb =new StringBuilder();
            sb.Append($"User:{User}, Score:{Point}, Date: {DateTime}");
            return sb.ToString();
        }
    }

再來, 我們建立Repository 負責資料存取(為了簡化說明所以沒有特意去串DB,而是直接將分數的資料建出來)
   
    public interface IRepository<T> where T:class
    {
       IEnumerable<T> GetAll();
    }
    public class ScoreRepository:IRepository<Score>
    {
        public  IEnumerable<Score> GetAll()
        {
            var scores = new List<Score>();
            scores.Add(new Score("Andy", 7, DateTime.Now));
            scores.Add(new Score("Andy", 20, DateTime.Now.AddDays(-7)));
            scores.Add(new Score("Andy", 13, DateTime.Now));
            scores.Add(new Score("Bob", 6, DateTime.Now));
            scores.Add(new Score("Bob", 19, DateTime.Now));
            scores.Add(new Score("Calvin", 35, DateTime.Now.AddDays(-30)));
            return scores;
        }
    }


建立Service 負責處理較複雜的商業邏輯來獲得最的高分數(這邊_scoRepository 宣告為Interface的目的是想提高程式的可測試性,將來做Unit test的時候我們就可以去Mock Repository)
    public class ScoreService
    {
        private IRepository<Score> _scoRepository;
        public ScoreService(IRepository<Score> scoRepository)
        {
            this._scoRepository = scoRepository;
        }
        public string GetHighScore()
        {
            return _scoRepository.GetAll().OrderByDescending(x => x.Point).FirstOrDefault()?.ToString();
        }
        public string GetHighScore(DateTime periodOfTime)
        {
            return _scoRepository.GetAll().Where(t => t.DateTime >= periodOfTime).OrderByDescending(x => x.Point).FirstOrDefault()?.ToString();
        }
    }






建立LUIS Dialog 處理對話
    [LuisModel("Your APP ID", "Your secret key")]
    [Serializable]
    public class LUISDialog: LuisDialog<object>
    {
       // Implement None Intent
       // Implement HighScore Intent
    }


LUIS dialog中為None Intent 新增一個方法
        [LuisIntent("")]
        [LuisIntent("None")]
        public async Task None(IDialogContext context, LuisResult result)
        {
            // Not a match -- Start a new Game
            //context.Call(new NumberGuesserDialog(), null);
            var reply = context.MakeMessage();
            reply.Text = "Sorry, I didn't get it";
            //Make you cortana speak
            reply.Speak = "Sorry, I can't understand";
            await context.PostAsync(reply);
            context.Wait(this.MessageReceived);
        }







實作回復最得高分的程式碼
        private async Task ShowHighScores(IDialogContext context, int paramDays)
        {
            // Get Yesterday
            var ParamYesterday = DateTime.Now.AddDays(paramDays);
            ScoreService scoreService=new ScoreService(new ScoreRepository());
            var scoreRecord = scoreService.GetHighScore(ParamYesterday);
            // Create a reply message
            var resultMessage = context.MakeMessage();
            resultMessage.Type = "message";
            resultMessage.Text = scoreRecord;
            resultMessage.Speak = scoreRecord;
            // Send Message
            await context.PostAsync(resultMessage);
        }

由於LUIS傳回來的Entity不見得會是數字
(例如:show me the high score for the past two days, 其中two 就是Entity)
所以我們須將英文數字轉成int, 讓我們等一下在做日期加減時能夠正常運算
        static int ParseEnglish(string number)
        {
            string[] words = number.ToLower().Split(new char[] {' ', '-', ','}, StringSplitOptions.RemoveEmptyEntries);
            string[] ones = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
            string[] teens =
                {"eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"};
            string[] tens = {"ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"};
            Dictionary<string, int> modifiers = new Dictionary<string, int>()
            {
                {"billion", 1000000000},
                {"million", 1000000},
                {"thousand", 1000},
                {"hundred", 100}
            };
            if (number == "eleventy billion")
                return int.MaxValue; // 110,000,000,000 is out of range for an int!
            int result = 0;
            int currentResult = 0;
            int lastModifier = 1;
            foreach (string word in words)
            {
                if (modifiers.ContainsKey(word))
                {
                    lastModifier *= modifiers[word];
                }
                else
                {
                    int n;
                    if (lastModifier > 1)
                    {
                        result += currentResult * lastModifier;
                        lastModifier = 1;
                        currentResult = 0;
                    }
                    if ((n = Array.IndexOf(ones, word) + 1) > 0)
                    {
                        currentResult += n;
                    }
                    else if ((n = Array.IndexOf(teens, word) + 1) > 0)
                    {
                        currentResult += n + 10;
                    }
                    else if ((n = Array.IndexOf(tens, word) + 1) > 0)
                    {
                        currentResult += n * 10;
                    }
                    else if (word != "and")
                    {
                        throw new ApplicationException("Unrecognized word: " + word);
                    }
                }
            }
            return result + currentResult * lastModifier;
        }


LUIS dialog中為HighScore Intent 新增一個方法
        [LuisIntent("HighScores")]
        public async Task GetHighScore(IDialogContext context, LuisResult result)
        {
            bool isIntentMatch = false;
            foreach (var objIntent in result.Intents)
            {
                if (objIntent.Intent == "HighScores" && objIntent.Score >= .98f)
                {
                    isIntentMatch = true;
                }
            }
            if (!isIntentMatch)
            {
                var reply = context.MakeMessage();
                reply.Type = "message";
                reply.Text = "I don't understand";
                reply.Speak = "I don't understand";
                await context.PostAsync(reply);
                context.Wait(this.MessageReceived);
                return;
            }
            // Determine the days in the past
            // to search for High Scores
            int intDays = -1;
            #region PeriodOfTime
            EntityRecommendation PeriodOfTime;
            if (result.TryFindEntity("PeriodOfTime", out PeriodOfTime))
            {
                switch (PeriodOfTime.Entity)
                {
                    case "month":
                        intDays = -30;
                        break;
                    case "day":
                        intDays = -1;
                        break;
                    case "week":
                        intDays = -7;
                        break;
                    default:
                        intDays = -1;
                        break;
                }
            }
            #endregion
            #region Days
            EntityRecommendation Days;
            if (result.TryFindEntity("Days", out Days))
            {
                // Set Days
                int intTempDays;
                if (int.TryParse(Days.Entity, out intTempDays))
                {
                    // A Number was passed
                    intDays = (Convert.ToInt32(intTempDays) * (-1));
                }
                else
                {
                    // A number was not passed
                    // Call ParseEnglish Method
                    // From: http://stackoverflow.com/questions/11278081/convert-words-string-to-int
                    intTempDays = ParseEnglish(Days.Entity);
                    intDays = (Convert.ToInt32(intTempDays) * (-1));
                }
                // 30 days maximum
                if (intDays > 30)
                {
                    intDays = 30;
                }
            }
            #endregion
            await ShowHighScores(context, intDays);
            context.Wait(this.MessageReceived);
        }


最後在MessagesController.cs中使用LUISDialog
        public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
        {
            if (activity.Type == ActivityTypes.Message)
            {
                Conversation.SendAsync(activity, ()=> new LUISDialog());
            }
            else
            {
                HandleSystemMessage(activity);
            }
            var response = Request.CreateResponse(HttpStatusCode.OK);
            return response;
        }

做到這裡差不多可以收工了><

以下是測試結果:



Source code:
https://github.com/andy51002000/MyLUISBot.git

留言