Esempio n. 1
0
        /// <summary>
        /// 会話を終了し、保存されているコンテキスト情報は削除されます。
        /// 中断中のスキルがあればひとつ前の中断スキルに戻るためのクイックリプライを返します。
        /// </summary>
        /// <returns></returns>
        private async Task <QuickReply> FinishAndGetResumeQuickReplyAsync(Context context)
        {
            var entityId = new EntityId(nameof(ContextEntity), $"{context.SkillName}-{context.UserId}");
            await DurableClient.SignalEntityAsync <IContextEntity>(entityId, proxy => proxy.SetContext(null));

            QuickReply ret = null;

            // 中断スキルへのジャンプおよびバッチ実行の後続スキル呼び出し
            // ユーザーIDで継続中のコンテキストを検索
            var result = await DurableClient.ListEntitiesAsync(
                new EntityQuery { EntityName = nameof(ContextEntity) },
                new System.Threading.CancellationToken());

            var entityQuery = result.Entities
                              .Where(e => e.EntityId.EntityKey.EndsWith(context.UserId) &&
                                     !e.EntityId.EntityKey.StartsWith(context.SkillName) &&
                                     !e.EntityId.EntityKey.StartsWith(WaiterEntityPrefix))
                              .OrderByDescending(e => e.LastOperationTime);

            Context targetContext = null;

            foreach (var e in entityQuery)
            {
                var state = await DurableClient.ReadEntityStateAsync <ContextEntity>(e.EntityId);

                var target = state.EntityState;
                if (state.EntityState.Context != null)
                {
                    targetContext = state.EntityState.Context;
                    break;
                }
            }

            if (targetContext != null)
            {
                // 保存されているコンテキストがバッチ実行中のものかを確認
                var batchSkills = targetContext.UserQuery.SubSkills;

                // バッチ実行の場合
                if (batchSkills != null && batchSkills.Length != 0 &&
                    // 今回完了したスキルがバッチ内のものかを調べる
                    batchSkills.Where((s, idx) =>
                                      s.UserQuery.IntentName == context.UserQuery.IntentName && // 今回完了したスキルがバッチ実行定義に存在
                                      !s.IsFinished &&                                          // 完了していない
                                      (idx == 0 || batchSkills[idx - 1].IsFinished)).Any())     // 先頭スキル or 直前が完了
                {
                    var skill = batchSkills.First(s => s.UserQuery.IntentName == context.UserQuery.IntentName);
                    var index = Array.IndexOf(batchSkills, skill);

                    var targetEntityId = new EntityId(nameof(ContextEntity), $"{targetContext.UserQuery.IntentName}-{context.UserId}");

                    if (batchSkills.Length == index + 1)
                    {
                        // 最終スキルなので、バッチ実行コンテキストを削除
                        await DurableClient.SignalEntityAsync <IContextEntity>(targetEntityId, proxy => proxy.SetContext(null));
                    }
                    else
                    {
                        // 終了フラグを更新
                        batchSkills[index].IsFinished = true;
                        await DurableClient.SignalEntityAsync <IContextEntity>(targetEntityId, proxy => proxy.SetContext(targetContext));

                        // 後続へジャンプ
                        var next = batchSkills[index + 1];
                        next.UserQuery.IsSubSkill = true;

                        ret = new QuickReply(new List <QuickReplyButtonObject>
                        {
                            new QuickReplyButtonObject(new PostbackTemplateAction(
                                                           $"続けて{next.DisplayName}に進む",
                                                           JsonConvert.SerializeObject(next.UserQuery)))
                        });
                    }
                }
                else
                {
                    var userQuery = targetContext.UserQuery;

                    // 子スキル情報、フルフィルメントテキストは削除(Postbackデータに収まらないため)
                    userQuery.SubSkills       = null;
                    userQuery.FulfillmentText = string.Empty;

                    ret = new QuickReply(new List <QuickReplyButtonObject>
                    {
                        new QuickReplyButtonObject(new PostbackTemplateAction(
                                                       "ひとつ前の対話を再開する",
                                                       JsonConvert.SerializeObject(userQuery)))
                    });
                }
            }
            return(ret);
        }
Esempio n. 2
0
        protected override async Task OnMessageAsync(MessageEvent ev)
        {
            await OnBeforeMessageAsync(ev);

            if (ShouldEnd)
            {
                return;
            }

            UserQuery query = null;

            switch (ev.Message.Type)
            {
            case EventMessageType.Text:
                var text = ((TextEventMessage)ev.Message).Text;

                // テキスト・インテント待ちを確認
                var waiterId    = new EntityId(nameof(ContextEntity), $"{WaiterEntityPrefix}_{ev.Source.UserId}");
                var waiterState = await DurableClient.ReadEntityStateAsync <ContextEntity>(waiterId);

                var     isWaitingForIntents = false;
                var     isWaitingForText    = false;
                Context waitingContext      = null;
                if (waiterState.EntityExists && waiterState.EntityState.Context != null)
                {
                    waitingContext = waiterState.EntityState.Context;
                    if (waitingContext.ExpectedIntentNames != null)
                    {
                        isWaitingForIntents = true;
                    }
                    else
                    {
                        isWaitingForText = true;
                    }
                }

                if (isWaitingForIntents || !isWaitingForText)
                {
                    // detect intent
                    query = await NluClient.DetectIntent(text, ev.Source.UserId);

                    Logger.LogInformation(query.IntentName);
                }

                if (isWaitingForIntents)
                {
                    Logger.LogInformation(waitingContext.SkillName);
                }

                // 実行スキルを選択
                var targetSkill = (isWaitingForIntents && waitingContext.ExpectedIntentNames.Contains(query.IntentName)) || isWaitingForText
                        ? waitingContext.SkillName  // 待機スキル
                        : query.IntentName;         // 解析スキル

                if (isWaitingForText)
                {
                    query = new UserQuery
                    {
                        IntentName = targetSkill,
                    };
                }

                // テキストをセット
                query.Text = text;

                var skill = Skills.FirstOrDefault(s => s.IntentName == targetSkill);
                if (skill != null)
                {
                    // コンテキスト確認を行う
                    var entityId = new EntityId(nameof(ContextEntity), $"{targetSkill}-{ev.Source.UserId}");
                    var state    = await DurableClient.ReadEntityStateAsync <ContextEntity>(entityId);

                    Context context = null;

                    if (state.EntityExists && state.EntityState.Context != null)
                    {
                        context = state.EntityState.Context;
                        var savedTimestamp = context.UserQuery.Timestamp;

                        // Execute incomplete sub-skills when restarting from a pause between sub-skills of a batch execution skill
                        var resume = context.UserQuery.SubSkills?.Where((s, idx) => idx != 0 && !s.IsFinished).FirstOrDefault();
                        if (resume != null)
                        {
                            context = new Context
                            {
                                UserId    = ev.Source.UserId,
                                SkillName = resume.UserQuery.IntentName,
                                UserQuery = resume.UserQuery
                            };
                        }
                        else
                        {
                            context.UserQuery = query;
                        }
                    }
                    else
                    {
                        context = new Context {
                            UserId = ev.Source.UserId, IsNew = true
                        };
                        query.Timestamp   = DateTime.UtcNow.Ticks;
                        context.UserQuery = query;
                    }
                    context.SkillName = targetSkill;

                    var messages = await skill.GetReplyMessagesAsync(context);

                    // Save context
                    await DurableClient.SignalEntityAsync <IContextEntity>(entityId, proxy => proxy.SetContext(context));

                    // Waiter
                    if (context.IsWaiting)
                    {
                        await DurableClient.SignalEntityAsync <IContextEntity>(waiterId, proxy => proxy.SetContext(context));
                    }

                    if (messages != null)
                    {
                        if (!skill.IsContinued)
                        {
                            var quickReply = await FinishAndGetResumeQuickReplyAsync(context);

                            if (messages.Last().QuickReply != null && messages.Last().QuickReply.Items.Count > 0)
                            {
                                messages.Last().QuickReply = MergeQuickReply(messages.Last().QuickReply, quickReply);
                            }
                            else
                            {
                                messages.Last().QuickReply = quickReply;
                            }
                        }
                        await LineMessagingClient.ReplyMessageAsync(ev.ReplyToken, messages);
                    }
                    // Batch execution skill
                    else if (context.UserQuery.SubSkills != null)
                    {
                        await StartBatchSubSkills(ev.ReplyToken, context);
                    }
                }
                else if (query.IsFallback)
                {
                    Logger.LogError("Fallback");
                    // TODO connect to knowledge base
                    await LineMessagingClient.ReplyMessageAsync(ev.ReplyToken,
                                                                query.FulfillmentText ?? "すみません、よくわかりませんでした。");
                }
                else
                {
                    Logger.LogError("Intentに対応するスキル定義がありません。");
                    await LineMessagingClient.ReplyMessageAsync(ev.ReplyToken,
                                                                query.FulfillmentText ?? "すみません、よくわかりませんでした。");
                }

                break;

            case EventMessageType.Sticker:
            case EventMessageType.Image:
            case EventMessageType.Video:
            case EventMessageType.Location:
            case EventMessageType.Audio:
            case EventMessageType.File:
            default:
                break;
            }

            await OnAfterMessageAsync(ev, query);
        }
Esempio n. 3
0
        protected override async Task OnPostbackAsync(PostbackEvent ev)
        {
            var query = JsonConvert.DeserializeObject <UserQuery>(ev.Postback.Data);

            await OnBeforePostbackAsync(ev, query);

            if (ShouldEnd)
            {
                return;
            }

            // テキスト・インテント待ちを確認
            var waiterId    = new EntityId(nameof(ContextEntity), $"{WaiterEntityPrefix}_{ev.Source.UserId}");
            var waiterState = await DurableClient.ReadEntityStateAsync <ContextEntity>(waiterId);

            var     isWaitingForIntents = false;
            var     isWaitingForText    = false;
            Context waitingContext      = null;

            if (waiterState.EntityExists && waiterState.EntityState.Context != null)
            {
                waitingContext = waiterState.EntityState.Context;
                if (waitingContext.ExpectedIntentNames != null)
                {
                    isWaitingForIntents = true;
                }
                else
                {
                    isWaitingForText = true;
                }
            }

            // 実行スキルを選択
            var targetSkill = (isWaitingForIntents && waitingContext.ExpectedIntentNames.Contains(query.IntentName)) || isWaitingForText
                ? waitingContext.SkillName  // 待機スキル
                : query.IntentName;         // 解析スキル

            if (isWaitingForText)
            {
                query = new UserQuery
                {
                    IntentName = targetSkill,
                };
            }

            var skill = Skills.FirstOrDefault(s => s.IntentName == targetSkill);

            if (skill != null)
            {
                var requestedTimestamp = query.Timestamp;

                // コンテキスト確認を行う
                var entityId = new EntityId(nameof(ContextEntity), $"{targetSkill}-{ev.Source.UserId}");
                var state    = await DurableClient.ReadEntityStateAsync <ContextEntity>(entityId);

                Context context = null;

                if (state.EntityExists && state.EntityState.Context != null)
                {
                    context = state.EntityState.Context;
                }
                else
                {
                    context = new Context {
                        UserId = ev.Source.UserId, IsNew = true
                    };
                    query.Timestamp = DateTime.UtcNow.Ticks;
                }

                context.UserQuery = query;

                if (!context.UserQuery.IsSubSkill && !context.UserQuery.AllowExternalCalls &&
                    (context.IsNew || context.UserQuery.Timestamp > requestedTimestamp))
                {
                    await LineMessagingClient.ReplyMessageAsync(ev.ReplyToken, "その操作は現在できません。");

                    return;
                }

                // スキル再確認
                var subSkill  = Skills.FirstOrDefault(s => s.IntentName == context.UserQuery.IntentName);
                var execSkill = subSkill ?? skill;

                var messages = await execSkill.GetReplyMessagesAsync(context);

                // 状態を保存
                await DurableClient.SignalEntityAsync <IContextEntity>(entityId, proxy => proxy.SetContext(context));

                // Waiter
                if (context.IsWaiting)
                {
                    await DurableClient.SignalEntityAsync <IContextEntity>(waiterId, proxy => proxy.SetContext(context));
                }

                if (messages != null)
                {
                    if (!execSkill.IsContinued)
                    {
                        var quickReply = await FinishAndGetResumeQuickReplyAsync(context);

                        if (messages.Last().QuickReply != null && messages.Last().QuickReply.Items.Count > 0)
                        {
                            messages.Last().QuickReply = MergeQuickReply(messages.Last().QuickReply, quickReply);
                        }
                        else
                        {
                            messages.Last().QuickReply = quickReply;
                        }
                    }
                    await LineMessagingClient.ReplyMessageAsync(ev.ReplyToken, messages);
                }
                // バッチ実行スキル
                else if (context.UserQuery.SubSkills != null)
                {
                    await StartBatchSubSkills(ev.ReplyToken, context);
                }
            }

            await OnAfterPostbackAsync(ev, query);
        }