/// <summary> /// Map Skill slots to what we have in SkillContext. /// </summary> /// <param name="innerDc">Dialog Contect.</param> /// <param name="actionSlots">The Slots within an Action.</param> /// <param name="skillContext">Calling Bot's SkillContext.</param> /// <returns>A filtered SkillContext for the Skill.</returns> private async Task <SkillContext> MatchSkillContextToSlots(DialogContext innerDc, List <Slot> actionSlots, SkillContext skillContext) { SkillContext slots = new SkillContext(); if (actionSlots != null) { foreach (Slot slot in actionSlots) { // For each slot we check to see if there is an exact match, if so we pass this slot across to the skill if (skillContext.TryGetValue(slot.Name, out object slotValue)) { slots.Add(slot.Name, slotValue); // Send trace to emulator await innerDc.Context.SendActivityAsync(new Activity(type : ActivityTypes.Trace, text : $"-->Matched the {slot.Name} slot within SkillContext and passing to the Skill.")); } } } return(slots); }
/// <summary> /// When a SkillDialog is started, a skillBegin event is sent which firstly indicates the Skill is being invoked in Skill mode, also slots are also provided where the information exists in the parent Bot. /// </summary> /// <param name="innerDc">inner dialog context.</param> /// <param name="options">options.</param> /// <param name="cancellationToken">cancellation token.</param> /// <returns>dialog turn result.</returns> protected override async Task <DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options = null, CancellationToken cancellationToken = default(CancellationToken)) { SkillContext slots = new SkillContext(); // Retrieve the SkillContext state object to identify slots (parameters) that can be used to slot-fill when invoking the skill var accessor = _userState.CreateProperty <SkillContext>(nameof(SkillContext)); var skillContext = await accessor.GetAsync(innerDc.Context, () => new SkillContext()); /* In instances where the caller is able to identify/specify the action we process the Action specific slots * In other scenarios (aggregated skill dispatch) we evaluate all possible slots against context and pass across * enabling the Skill to perform it's own action identification. */ var actionName = options != null ? options as string : null; if (actionName != null) { // Find the specified within the selected Skill for slot filling evaluation var action = _skillManifest.Actions.SingleOrDefault(a => a.Id == actionName); if (action != null) { // If the action doesn't define any Slots or SkillContext is empty then we skip slot evaluation if (action.Definition.Slots != null && skillContext.Count > 0) { // Match Slots to Skill Context slots = await MatchSkillContextToSlots(innerDc, action.Definition.Slots, skillContext); } } else { throw new ArgumentException($"Passed Action ({actionName}) could not be found within the {_skillManifest.Id} skill manifest action definition."); } } else { // The caller hasn't got the capability of identifying the action as well as the Skill so we enumerate // actions and slot data to pass what we have // Retrieve a distinct list of all slots, some actions may use the same slot so we use distinct to ensure we only get 1 instance. var skillSlots = _skillManifest.Actions.SelectMany(s => s.Definition.Slots).Distinct(new SlotEqualityComparer()); if (skillSlots != null) { // Match Slots to Skill Context slots = await MatchSkillContextToSlots(innerDc, skillSlots.ToList(), skillContext); } } await innerDc.Context.SendActivityAsync(new Activity(type : ActivityTypes.Trace, text : $"-->Handing off to the {_skillManifest.Name} skill.")); var activity = innerDc.Context.Activity; var skillBeginEvent = new Activity( type: ActivityTypes.Event, channelId: activity.ChannelId, from: new ChannelAccount(id: activity.From.Id, name: activity.From.Name), recipient: new ChannelAccount(id: activity.Recipient.Id, name: activity.Recipient.Name), conversation: new ConversationAccount(id: activity.Conversation.Id), name: SkillEvents.SkillBeginEventName, value: slots); // Send skillBegin event to Skill/Bot return(await ForwardToSkillAsync(innerDc, skillBeginEvent)); }