From 42fb90f6d75135166691dc42ba00ebf7e78f31a6 Mon Sep 17 00:00:00 2001 From: rabidgremlin Date: Tue, 31 Jul 2018 14:11:55 +1200 Subject: [PATCH] Default slot support for templated intents (#11) * Updated ver num. * Added default value slot support to templated intents. * Added customslot with default value. * More work on making templated intents default value aware. --- gradle.properties | 2 +- .../AbstractMachineLearningIntentMatcher.java | 12 +++++ .../mutters/core/util/SessionUtils.java | 53 +++++++++++++++--- .../slots/CustomSlotWithDefaultValue.java | 54 +++++++++++++++++++ .../mutters/templated/TemplatedIntent.java | 36 +++++++++---- .../mutters/TestIntentMatcher.java | 54 +++++++++++++++++++ 6 files changed, 193 insertions(+), 18 deletions(-) create mode 100644 mutters-slots/src/main/java/com/rabidgremlin/mutters/slots/CustomSlotWithDefaultValue.java diff --git a/gradle.properties b/gradle.properties index 90888764..73bea5cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=4.2.1 +version=4.3.0-SNAPSHOT # These are place holder values. Real values should be set in user's home gradle.properties # and are only needed when signing and uploading to central maven repo diff --git a/mutters-core/src/main/java/com/rabidgremlin/mutters/core/ml/AbstractMachineLearningIntentMatcher.java b/mutters-core/src/main/java/com/rabidgremlin/mutters/core/ml/AbstractMachineLearningIntentMatcher.java index 81beead0..c3a1a9dc 100644 --- a/mutters-core/src/main/java/com/rabidgremlin/mutters/core/ml/AbstractMachineLearningIntentMatcher.java +++ b/mutters-core/src/main/java/com/rabidgremlin/mutters/core/ml/AbstractMachineLearningIntentMatcher.java @@ -1,5 +1,7 @@ package com.rabidgremlin.mutters.core.ml; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Set; import java.util.SortedMap; @@ -86,6 +88,16 @@ public void addIntent(Intent intent) { intents.put(intent.getName().toUpperCase(), intent); } + + /** + * Returns the intents for this matcher. + * + * @return The intents for this matcher + */ + public Collection getIntents() + { + return Collections.unmodifiableCollection(intents.values()); + } /* * (non-Javadoc) diff --git a/mutters-core/src/main/java/com/rabidgremlin/mutters/core/util/SessionUtils.java b/mutters-core/src/main/java/com/rabidgremlin/mutters/core/util/SessionUtils.java index d4f31bdc..3b362342 100644 --- a/mutters-core/src/main/java/com/rabidgremlin/mutters/core/util/SessionUtils.java +++ b/mutters-core/src/main/java/com/rabidgremlin/mutters/core/util/SessionUtils.java @@ -2,6 +2,8 @@ import org.joda.time.LocalDate; import org.joda.time.LocalTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.rabidgremlin.mutters.core.IntentMatch; import com.rabidgremlin.mutters.core.SlotMatch; @@ -17,6 +19,9 @@ */ public class SessionUtils { + /** Logger.*/ + private static final Logger LOG = LoggerFactory.getLogger(SessionUtils.class); + /** Prefix for slot values stored in session to avoid any name collisions. */ public static final String SLOT_PREFIX = "SLOT_JLA1974_"; @@ -163,8 +168,16 @@ public static String getStringSlot(IntentMatch match, String slotName, String de { if (match.getSlotMatch(slotName) != null && match.getSlotMatch(slotName).getValue() != null) { - // TODO better cast handling - return (String) match.getSlotMatch(slotName).getValue(); + try + { + return (String) match.getSlotMatch(slotName).getValue(); + } + catch(ClassCastException e) + { + // failed to cast so assume invalid string and return default + LOG.warn("Non String value: {} found in slot {}", match.getSlotMatch(slotName).getValue(), slotName); + return defaultValue; + } } else { @@ -184,8 +197,16 @@ public static Number getNumberSlot(IntentMatch match, String slotName, Number de { if (match.getSlotMatch(slotName) != null && match.getSlotMatch(slotName).getValue() != null) { - // TODO better cast handling - return (Number) match.getSlotMatch(slotName).getValue(); + try + { + return (Number) match.getSlotMatch(slotName).getValue(); + } + catch(ClassCastException e) + { + // failed to cast so assume invalid number and return default + LOG.warn("Non Number value: {} found in slot {}", match.getSlotMatch(slotName).getValue(), slotName); + return defaultValue; + } } else { @@ -206,8 +227,16 @@ public static LocalDate getLocalDateSlot(IntentMatch match, String slotName, { if (match.getSlotMatch(slotName) != null && match.getSlotMatch(slotName).getValue() != null) { - // TODO better cast handling - return (LocalDate) match.getSlotMatch(slotName).getValue(); + try + { + return (LocalDate) match.getSlotMatch(slotName).getValue(); + } + catch(ClassCastException e) + { + // failed to cast so assume invalid localdate and return default + LOG.warn("Non LocalDate value: {} found in slot {}", match.getSlotMatch(slotName).getValue(), slotName); + return defaultValue; + } } else { @@ -228,8 +257,16 @@ public static LocalTime getLocalTimeSlot(IntentMatch match, String slotName, { if (match.getSlotMatch(slotName) != null && match.getSlotMatch(slotName).getValue() != null) { - // TODO better cast handling - return (LocalTime) match.getSlotMatch(slotName).getValue(); + try + { + return (LocalTime) match.getSlotMatch(slotName).getValue(); + } + catch(ClassCastException e) + { + // failed to cast so assume invalid localtime and return default + LOG.warn("Non LocalTime value: {} found in slot {}", match.getSlotMatch(slotName).getValue(), slotName); + return defaultValue; + } } else { diff --git a/mutters-slots/src/main/java/com/rabidgremlin/mutters/slots/CustomSlotWithDefaultValue.java b/mutters-slots/src/main/java/com/rabidgremlin/mutters/slots/CustomSlotWithDefaultValue.java new file mode 100644 index 00000000..2c6c9964 --- /dev/null +++ b/mutters-slots/src/main/java/com/rabidgremlin/mutters/slots/CustomSlotWithDefaultValue.java @@ -0,0 +1,54 @@ +package com.rabidgremlin.mutters.slots; + +import java.util.HashMap; + +import com.rabidgremlin.mutters.core.Context; +import com.rabidgremlin.mutters.core.Slot; +import com.rabidgremlin.mutters.core.SlotMatch; + +/** + * A CustomSlot that supports default values. + * + * @author rabidgremlin + * + */ + public class CustomSlotWithDefaultValue + extends CustomSlot + implements DefaultValueSlot +{ + /** The default value to return */ + private Object defaultValue; + + /** + * Constructor. + * + * @param name The name of the slot. + * @param optionValueMap A map of possible input values mapped to output values. + * @param defaultValue The default value. + */ + public CustomSlotWithDefaultValue(String name, HashMap optionValueMap, Object defaultValue) + { + super(name, optionValueMap); + this.defaultValue = defaultValue; + } + + /** + * Constructor. + * + * @param name The name of the slot. + * @param options The list of expected options. + * @param defaultValue The default value. + */ + public CustomSlotWithDefaultValue(String name, String[] options, Object defaultValue) + { + super(name, options); + this.defaultValue = defaultValue; + } + + @Override + public Object getDefaultValue() + { + return defaultValue; + } + +} \ No newline at end of file diff --git a/mutters-templated-intent/src/main/java/com/rabidgremlin/mutters/templated/TemplatedIntent.java b/mutters-templated-intent/src/main/java/com/rabidgremlin/mutters/templated/TemplatedIntent.java index bc6b9099..aebb072f 100644 --- a/mutters-templated-intent/src/main/java/com/rabidgremlin/mutters/templated/TemplatedIntent.java +++ b/mutters-templated-intent/src/main/java/com/rabidgremlin/mutters/templated/TemplatedIntent.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import org.slf4j.Logger; @@ -9,7 +10,10 @@ import com.rabidgremlin.mutters.core.Context; import com.rabidgremlin.mutters.core.Intent; +import com.rabidgremlin.mutters.core.Slot; +import com.rabidgremlin.mutters.core.SlotMatch; import com.rabidgremlin.mutters.core.Tokenizer; +import com.rabidgremlin.mutters.slots.DefaultValueSlot; /** * This is an intent that matches based on an utterance template. @@ -81,21 +85,35 @@ public List addUtterances(List utteranceTemplates) * @return The templated utterance match. */ public TemplatedUtteranceMatch matches(String[] input, Context context) - { - log.debug("------------- Intent: {} Input: {}", name, input); + { for (TemplatedUtterance utterance : utterances) - { - log.debug(" Matching to {} ", utterance.getTemplate()); + { TemplatedUtteranceMatch match = utterance.matches(input, slots, context); if (match.isMatched()) - { - log.debug("------------ Matched to {} match: {} -------------", utterance.getTemplate(), - match); + { + // matched utterance didn't fill in all the slots so check for default values + if (match.getSlotMatches().size() != slots.getSlots().size()) + { + // grab all the matched slots + HashMap matchedSlots = match.getSlotMatches(); + + // loop through each slot + for (Slot slot: slots.getSlots()) + { + // does slot have default value and no match ? + if (slot instanceof DefaultValueSlot && !matchedSlots.containsKey(slot)) + { + // yep create a slot match with default value + Object defaultValue = ((DefaultValueSlot)slot).getDefaultValue(); + matchedSlots.put(slot, new SlotMatch(slot,defaultValue == null?"":defaultValue.toString(),defaultValue)); + } + } + } + return match; } } - - log.debug("------------ No Match to {} -------------", name); + return new TemplatedUtteranceMatch(false); } diff --git a/mutters-templated-intent/src/test/java/com/rabidgremlin/mutters/TestIntentMatcher.java b/mutters-templated-intent/src/test/java/com/rabidgremlin/mutters/TestIntentMatcher.java index 5bd5479b..98b48deb 100644 --- a/mutters-templated-intent/src/test/java/com/rabidgremlin/mutters/TestIntentMatcher.java +++ b/mutters-templated-intent/src/test/java/com/rabidgremlin/mutters/TestIntentMatcher.java @@ -11,6 +11,7 @@ import com.rabidgremlin.mutters.core.IntentMatch; import com.rabidgremlin.mutters.core.SlotMatch; import com.rabidgremlin.mutters.slots.CustomSlot; +import com.rabidgremlin.mutters.slots.DefaultValueSlot; import com.rabidgremlin.mutters.slots.LiteralSlot; import com.rabidgremlin.mutters.slots.NumberSlot; import com.rabidgremlin.mutters.templated.SimpleTokenizer; @@ -117,5 +118,58 @@ public void testHandlingOfEmptyOrTokenizedOutInputs() intentMatch = matcher.match(" ?", new Context(), null, null); assertThat(intentMatch, is(nullValue())); } + + // slot that matches a colour or defaults to black + class ColorsSlot extends CustomSlot implements DefaultValueSlot + { + public ColorsSlot() + { + super("Color",new String[] {"Red","Green","Blue","White"}); + } + + @Override + public Object getDefaultValue() + { + return "Black"; + } + } + + @Test + public void testDefaultValueHandling() + { + TemplatedIntentMatcher matcher = new TemplatedIntentMatcher(tokenizer); + + TemplatedIntent intent = matcher.addIntent("FavColourIntent"); + + ColorsSlot colorSlot = new ColorsSlot(); + intent.addSlot(colorSlot); + + intent.addUtterance("My favourite color is {Color}"); + intent.addUtterance("{Color} is my favourite"); + intent.addUtterance("I have a favourite color"); // utterance without a slot, should default to Black + + IntentMatch intentMatch = matcher.match("My favourite color is green", new Context(), null, null); + assertThat(intentMatch, is(notNullValue())); + assertThat(intentMatch.getSlotMatches().size(), is(1)); + SlotMatch colourMatch = intentMatch.getSlotMatches().get(colorSlot); + assertThat(colourMatch, is(notNullValue())); + assertThat(colourMatch.getValue(), is("Green")); + + intentMatch = matcher.match("Red is my favourite", new Context(), null, null); + assertThat(intentMatch, is(notNullValue())); + assertThat(intentMatch.getSlotMatches().size(), is(1)); + colourMatch = intentMatch.getSlotMatches().get(colorSlot); + assertThat(colourMatch, is(notNullValue())); + assertThat(colourMatch.getValue(), is("Red")); + + + intentMatch = matcher.match("I have a favourite color", new Context(), null, null); + assertThat(intentMatch, is(notNullValue())); + assertThat(intentMatch.getSlotMatches().size(), is(1)); + colourMatch = intentMatch.getSlotMatches().get(colorSlot); + assertThat(colourMatch, is(notNullValue())); + assertThat(colourMatch.getValue(), is("Black")); + + } }