Skip to content
This repository has been archived by the owner on Jun 30, 2022. It is now read-only.

Updated Action/Slot matching approach #1182

Merged
merged 1 commit into from
Apr 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ public void AddSkills()
"testSkill/testActionWithSlots",
slots));

// Simple skill, with two actions and multiple slots
var multiParamSlots = new List<Slot>();
multiParamSlots.Add(new Slot("param1", new List<string>() { "string" }));
multiParamSlots.Add(new Slot("param2", new List<string>() { "string" }));
multiParamSlots.Add(new Slot("param3", new List<string>() { "string" }));

var multiActionSkill = ManifestUtilities.CreateSkill(
"testskillwithmultipleactionsandslots",
"testskillwithmultipleactionsandslots",
"https://testskillwithslots.tempuri.org/api/skill",
"testSkill/testAction1",
multiParamSlots);

multiActionSkill.Actions.Add(ManifestUtilities.CreateAction("testSkill/testAction2", multiParamSlots));
_skillManifests.Add(multiActionSkill);

// Each Skill has a number of actions, these actions are added as their own SkillDialog enabling
// the SkillDialog to know which action is invoked and identify the slots as appropriate.
foreach (var skill in _skillManifests)
Expand Down Expand Up @@ -124,7 +140,34 @@ await this.GetTestFlow(_skillManifests.Single(s => s.Name == "testskillwithslots
_mockSkillTransport.VerifyActivityForwardedCorrectly(activity => ValidateActivity(activity, eventToMatch));
}

private bool ValidateActivity(string activitySent, string activityToMatch)
/// <summary>
/// Ensure the skillBegin event is sent and includes the slots that were configured in the manifest
/// and present in State. This doesn't pass an action so "global" slot filling is used
/// </summary>
/// <returns>Task.</returns>
[TestMethod]
public async Task SkilllBeginEventNoActionPassed()
{
string eventToMatch = await File.ReadAllTextAsync(@".\TestData\skillBeginEventWithTwoParams.json");

var sp = Services.BuildServiceProvider();
var adapter = sp.GetService<TestAdapter>();

// Data to add to the UserState managed SkillContext made available for slot filling
// within SkillDialog
Dictionary<string, object> slots = new Dictionary<string, object>();
slots.Add("param1", "TEST");
slots.Add("param2", "TEST2");

// Not passing action to test the "global" slot filling behaviour
await this.GetTestFlow(_skillManifests.Single(s => s.Name == "testskillwithmultipleactionsandslots"), null, slots)
.Send("hello")
.StartTestAsync();

_mockSkillTransport.VerifyActivityForwardedCorrectly(activity => ValidateActivity(activity, eventToMatch));
}

private bool ValidateActivity(string activitySent, string activityToMatch)
{
return string.Equals(activitySent, activityToMatch);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,16 @@ public static SkillManifest CreateSkill(string id, string name, string endpoint,

return skillManifest;
}

public static Models.Manifest.Action CreateAction(string id, List<Models.Manifest.Slot> slots = null)
{
var action = new Models.Manifest.Action();

action.Id = id;
action.Definition = new ActionDefinition();
action.Definition.Slots = slots;

return action;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ActionDefinition
public string Description { get; set; }

[JsonProperty(PropertyName = "slots")]
public List<Slot> Slots { get; set; }
public List<Slot> Slots { get; set; } = new List<Slot>();

[JsonProperty(PropertyName = "triggers")]
public Triggers Triggers { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ public class SkillManifest
public AuthenticationConnection[] AuthenticationConnections { get; set; }

[JsonProperty(PropertyName = "actions")]
public List<Action> Actions { get; set; }
public List<Action> Actions { get; set; } = new List<Action>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,37 +91,40 @@ public override async Task EndDialogAsync(ITurnContext turnContext, DialogInstan
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 as string;
if (actionName == null)
{
throw new ArgumentException("SkillDialog requires an Action in order to be able to identify which Action within a skill to invoke.");
}
else
if (actionName != null)
{
// Find the Action within the selected Skill for slot filling evaluation
// 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)
{
foreach (Slot slot in action.Definition.Slots)
{
// 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
innerDc.Context.SendActivityAsync(new Activity(type: ActivityTypes.Trace, text: $"-->Matched the {slot.Name} slot within SkillContext and passing to the {actionName} action.")).GetAwaiter().GetResult();
}
}
// Match Slots to Skill Context
slots = await MatchSkillContextToSlots(innerDc, action.Definition.Slots, skillContext);
}
}
else
{
// Loosening checks for current Dispatch evaluation, TODO - Review
// throw new ArgumentException($"Passed Action ({actionName}) could not be found within the {_skillManifest.Id} skill manifest action definition.");
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);
}
}

Expand Down Expand Up @@ -184,6 +187,34 @@ public override async Task EndDialogAsync(ITurnContext turnContext, DialogInstan
return dialogResult;
}

/// <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>
/// Forward an inbound activity on to the Skill. This is a synchronous operation whereby all response activities are aggregated and returned in one batch.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Bot.Builder.Skills.Models.Manifest;

namespace Microsoft.Bot.Builder.Skills
{
public class SlotEqualityComparer : IEqualityComparer<Slot>
{
public bool Equals(Slot x, Slot y)
{
return x.Name.Equals(y.Name);
}

public int GetHashCode(Slot obj)
{
return obj.Name.GetHashCode();
}
}
}