Exemplo n.º 1
0
class IntentEngineTests(unittest.TestCase):
    def setUp(self):
        self.engine = IntentDeterminationEngine()

    def testRegisterIntentParser(self):
        assert len(self.engine.intent_parsers) == 0
        try:
            self.engine.register_intent_parser("NOTAPARSER")
            assert "Did not fail to register invalid intent parser" and False
        except ValueError as e:
            pass
        parser = IntentBuilder("Intent").build()
        self.engine.register_intent_parser(parser)
        assert len(self.engine.intent_parsers) == 1

    def testRegisterRegexEntity(self):
        assert len(self.engine._regex_strings) == 0
        assert len(self.engine.regular_expressions_entities) == 0
        self.engine.register_regex_entity(".*")
        assert len(self.engine._regex_strings) == 1
        assert len(self.engine.regular_expressions_entities) == 1

    def testSelectBestIntent(self):
        parser1 = IntentBuilder("Parser1").require("Entity1").build()
        self.engine.register_entity("tree", "Entity1")
        self.engine.register_intent_parser(parser1)

        utterance = "go to the tree house"
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        parser2 = IntentBuilder("Parser2").require("Entity1").require(
            "Entity2").build()
        self.engine.register_entity("house", "Entity2")
        self.engine.register_intent_parser(parser2)
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser2'

    def testIntentMissingEntity(self):
        utterance1 = "give me One home"
        utterance2 = "give me One or Two"
        parser3 = IntentBuilder("Parser3").require("One").require(
            "Two").build()
        self.engine.register_entity("One", "One")
        self.engine.register_entity("Two", "Two")
        self.engine.register_intent_parser(parser3)
        intent2 = self.engine.determine_intent(utterance2)
        try:
            intent2 = next(intent2)
        except BaseException:
            pass
        intent1 = self.engine.determine_intent(utterance1)
        try:
            intent1 = next(intent1)
        except BaseException:
            pass
class ContextManagerIntegrationTest(unittest.TestCase):
    def setUp(self):
        self.context_manager = ContextManager()
        self.engine = IntentDeterminationEngine()

    def testBasicContextualFollowup(self):
        intent1 = IntentBuilder("TimeQueryIntent")\
            .require("TimeQuery")\
            .require("Location")\
            .build()
        intent2 = IntentBuilder("WeatherQueryIntent")\
            .require("WeatherKeyword")\
            .require("Location")\
            .build()

        self.engine.register_intent_parser(intent1)
        self.engine.register_intent_parser(intent2)

        self.engine.register_entity("what time is it", "TimeQuery")
        self.engine.register_entity("seattle", "Location")
        self.engine.register_entity("miami", "Location")

        self.engine.register_entity("weather", "WeatherKeyword")

        utterance1 = "what time is it in seattle"
        intent = next(self.engine.determine_intent(utterance1, include_tags=True, context_manager=self.context_manager))
        assert intent
        assert intent['intent_type'] == 'TimeQueryIntent'
        assert '__tags__' in intent
        for tag in intent['__tags__']:
            context_entity = tag.get('entities')[0]
            self.context_manager.inject_context(context_entity)

        utterance2 = "what's the weather like?"
        intent = next(self.engine.determine_intent(utterance2, context_manager=self.context_manager))
        assert intent
        assert intent['intent_type'] == 'WeatherQueryIntent'

    def testContextOnlyUsedOnce(self):
        intent_parser = IntentBuilder("DummyIntent")\
            .require("Foo")\
            .optionally("Foo", "Foo2")\
            .build()

        context_entity = {'confidence': 1.0, 'data': [('foo', 'Foo')], 'match': 'foo', 'key': 'foo'}
        self.context_manager.inject_context(context_entity)
        self.engine.register_intent_parser(intent_parser)
        self.engine.register_entity("foo", "Foo")
        self.engine.register_entity("fop", "Foo")

        intent = next(self.engine.determine_intent("foo", include_tags=True, context_manager=self.context_manager))
        assert intent
        assert intent['intent_type'] == "DummyIntent"
        assert not (intent.get("Foo") and intent.get("Foo2"))
class ContextManagerIntegrationTest(unittest.TestCase):
    def setUp(self):
        self.context_manager = ContextManager()
        self.engine = IntentDeterminationEngine()

    def testBasicContextualFollowup(self):
        intent1 = IntentBuilder("TimeQueryIntent")\
            .require("TimeQuery")\
            .require("Location")\
            .build()
        intent2 = IntentBuilder("WeatherQueryIntent")\
            .require("WeatherKeyword")\
            .require("Location")\
            .build()

        self.engine.register_intent_parser(intent1)
        self.engine.register_intent_parser(intent2)

        self.engine.register_entity("what time is it", "TimeQuery")
        self.engine.register_entity("seattle", "Location")
        self.engine.register_entity("miami", "Location")

        self.engine.register_entity("weather", "WeatherKeyword")

        utterance1 = "what time is it in seattle"
        intent = next(self.engine.determine_intent(utterance1, include_tags=True, context_manager=self.context_manager))
        assert intent
        assert intent['intent_type'] == 'TimeQueryIntent'
        assert '__tags__' in intent
        for tag in intent['__tags__']:
            context_entity = tag.get('entities')[0]
            self.context_manager.inject_context(context_entity)

        utterance2 = "what's the weather like?"
        intent = next(self.engine.determine_intent(utterance2, context_manager=self.context_manager))
        assert intent
        assert intent['intent_type'] == 'WeatherQueryIntent'

    def testContextOnlyUsedOnce(self):
        intent_parser = IntentBuilder("DummyIntent")\
            .require("Foo")\
            .optionally("Foo", "Foo2")\
            .build()

        context_entity = {'confidence': 1.0, 'data': [('foo', 'Foo')], 'match': 'foo', 'key': 'foo'}
        self.context_manager.inject_context(context_entity)
        self.engine.register_intent_parser(intent_parser)
        self.engine.register_entity("foo", "Foo")
        self.engine.register_entity("fop", "Foo")

        intent = next(self.engine.determine_intent("foo", include_tags=True, context_manager=self.context_manager))
        assert intent
        assert intent['intent_type'] == "DummyIntent"
        assert not (intent.get("Foo") and intent.get("Foo2"))
Exemplo n.º 4
0
class SmartHomeIntentParser():
    def __init__(self):
        self.engine = IntentDeterminationEngine()
        # smart home intent vocabalory
        switch_tasks_keyword = ["switch", "turn"]
        for stk in switch_tasks_keyword:
            self.engine.register_entity(stk, "SwitchTasksKeyword")

        on_off_keyword = ["on", "off"]

        for stk in on_off_keyword:
            self.engine.register_entity(stk, "OnOffKeyword")

        equipment_keyword = ["lights", "light", "fan"]

        for stk in equipment_keyword:
            self.engine.register_entity(stk, "EquipmentKeyword")

        smart_home_intent = IntentBuilder("SmartHomeIntent")\
            .require("SwitchTasksKeyword")\
            .require("OnOffKeyword")\
            .require("EquipmentKeyword")\
            .build()

        self.engine.register_intent_parser(smart_home_intent)

    def parse(self, sentence):
        for intent in self.engine.determine_intent(sentence):
            if intent.get('confidence') > 0:
                return intent
class MusicPlayerIntentParser():
    def __init__(self):
        self.engine = IntentDeterminationEngine()
        # define music vocabulary
        music_verbs = ["listen", "hear", "play", "stop"]

        for mv in music_verbs:
            self.engine.register_entity(mv, "MusicVerb")

        music_keywords = ["songs", "music"]

        for mk in music_keywords:
            self.engine.register_entity(mk, "MusicKeyword")

        self.engine.register_regex_entity(
            "(play|hear|listen|listen to)\s*(the)?\s*(song|album)?\s*(?P<Media>.*)$"  # NoQA
        )

        music_intent = IntentBuilder("MusicIntent")\
            .require("MusicVerb")\
            .optionally("MusicKeyword")\
            .optionally("Media")\
            .build()

        self.engine.register_intent_parser(music_intent)

    def parse(self, sentence):
        for intent in self.engine.determine_intent(sentence):
            if intent.get('confidence') > 0:
                return intent
class IntentSkill(MycroftSkill):
    def __init__(self):
        MycroftSkill.__init__(self, name="IntentSkill")
        self.engine = IntentDeterminationEngine()

    def initialize(self):
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)

    def handle_utterance(self, message):
        utterances = message.data.get('utterances', '')

        best_intent = None
        for utterance in utterances:
            try:
                best_intent = next(self.engine.determine_intent(
                    utterance, 100))
                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration, e:
                logger.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            reply = message.reply(best_intent.get('intent_type'), best_intent)
            self.emitter.emit(reply)
        elif len(utterances) == 1:
            self.emitter.emit(
                Message("intent_failure", {"utterance": utterances[0]}))
        else:
            self.emitter.emit(
                Message("multi_utterance_intent_failure",
                        {"utterances": utterances}))
Exemplo n.º 7
0
class JokeIntentParser():
    def __init__(self):
        self.engine = IntentDeterminationEngine()
        joke_verbs = [
            "tell",
            "crack"
        ]
        for mv in joke_verbs:
            self.engine.register_entity(mv, "JokeVerb")

        joke_keywords = [
            "joke"
        ]
        for mk in joke_keywords:
            self.engine.register_entity(mk, "JokeKeyword")

        joke_intent = IntentBuilder("JokeIntent")\
            .require("JokeVerb")\
            .optionally("JokeKeyword")\
            .build()

        self.engine.register_intent_parser(joke_intent)

    def parse(self, sentence):
        for intent in self.engine.determine_intent(sentence):
            if intent.get('confidence') > 0:
                return intent
Exemplo n.º 8
0
    def testContextAndOneOf(self):
        # test to cover https://github.com/MycroftAI/adapt/issues/86
        engine = IntentDeterminationEngine()
        context_manager = ContextManager()

        # define vocabulary
        weather_keyword = ["weather"]

        for wk in weather_keyword:
            engine.register_entity(wk, "WeatherKeyword")

        # structure intent
        weather_intent = IntentBuilder("WeatherIntent") \
            .require("WeatherKeyword") \
            .one_of("Location", "LocationContext").build()

        engine.register_intent_parser(weather_intent)
        word = 'lizard'
        context = 'LocationContext'
        entity = {}
        entity['data'] = [(word, context)]
        entity['match'] = word
        entity['key'] = word
        context_manager.inject_context(entity)

        intents = list(
            engine.determine_intent('weather',
                                    context_manager=context_manager))
        self.assertEqual(1, len(intents), "Incorrect number of intents")
        result = intents[0]
        self.assertEqual("lizard", result.get("LocationContext"),
                         "Context not matched")
        self.assertEqual(0.75, result.get('confidence'),
                         "Context confidence not properly applied.")
Exemplo n.º 9
0
class IntentSkill(MycroftSkill):
    def __init__(self):
        MycroftSkill.__init__(self, name="IntentSkill")
        self.engine = IntentDeterminationEngine()

    def initialize(self):
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)

    def handle_utterance(self, message):
        utterances = message.metadata.get('utterances', '')

        best_intent = None
        for utterance in utterances:
            try:
                best_intent = next(self.engine.determine_intent(utterance, num_results=100))
                best_intent['utterance'] = utterance  # TODO - Should Adapt handle this?
            except StopIteration, e:
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            reply = message.reply(best_intent.get('intent_type'), metadata=best_intent)
            self.emitter.emit(reply)
        elif len(utterances) == 1:
            self.emitter.emit(Message("intent_failure", metadata={"utterance": utterances[0]}))
        else:
            self.emitter.emit(Message("multi_utterance_intent_failure", metadata={"utterances": utterances}))
Exemplo n.º 10
0
    def testEmptyTags(self):
        # Validates https://github.com/MycroftAI/adapt/issues/114
        engine = IntentDeterminationEngine()
        engine.register_entity("Kevin",
                               "who")  # same problem if several entities
        builder = IntentBuilder("Buddies")
        builder.optionally("who")  # same problem if several entity types
        engine.register_intent_parser(builder.build())

        intents = [i for i in engine.determine_intent("Julien is a friend")]
        assert len(intents) == 0
Exemplo n.º 11
0
class input_engine:
    """Manages the intent engine and natural language input parser"""
    def __init__(self):
        self.engine = IntentDeterminationEngine()

    def register_entity(self, keywords, name):
        """Registers an intenty to be found in an input"""
        for k in keywords:
            self.engine.register_entity(k, name)

    def register_intent(self, intent):
        """Registers an intent that can be found in an input"""
        self.engine.register_intent_parser(intent)

    def get_intent(self, input_string):
        """Returns an intent from an input string if one is found"""
        intent = self.engine.determine_intent(input_string)
        for intent in self.engine.determine_intent(input_string):
            if intent.get("confidence") > 0:
                return intent
        return None
Exemplo n.º 12
0
class IntentEngineTests(unittest.TestCase):
    def setUp(self):
        self.engine = IntentDeterminationEngine()

    def testRegisterIntentParser(self):
        assert len(self.engine.intent_parsers) == 0
        try:
            self.engine.register_intent_parser("NOTAPARSER")
            assert "Did not fail to register invalid intent parser" and False
        except ValueError as e:
            pass
        parser = IntentBuilder("Intent").build()
        self.engine.register_intent_parser(parser)
        assert len(self.engine.intent_parsers) == 1

    def testRegisterRegexEntity(self):
        assert len(self.engine._regex_strings) == 0
        assert len(self.engine.regular_expressions_entities) == 0
        self.engine.register_regex_entity(".*")
        assert len(self.engine._regex_strings) == 1
        assert len(self.engine.regular_expressions_entities) == 1

    def testSelectBestIntent(self):
        parser1 = IntentBuilder("Parser1").require("Entity1").build()
        self.engine.register_intent_parser(parser1)
        self.engine.register_entity("tree", "Entity1")

        utterance = "go to the tree house"
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        parser2 = IntentBuilder("Parser2").require("Entity1").require("Entity2").build()
        self.engine.register_intent_parser(parser2)
        self.engine.register_entity("house", "Entity2")
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser2'
Exemplo n.º 13
0
class IntentSkill(MycroftSkill):
    def __init__(self):
        MycroftSkill.__init__(self, name="IntentSkill")
        self.engine = IntentDeterminationEngine()
        self.reload_skill = False

    def initialize(self):
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)

    def handle_utterance(self, message):
        # Get language of the utterance
        lang = message.data.get('lang', None)
        if not lang:
            lang = "en-us"

        utterances = message.data.get('utterances', '')

        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(
                    self.engine.determine_intent(normalize(utterance, lang),
                                                 100))

                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration, e:
                logger.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            reply = message.reply(best_intent.get('intent_type'), best_intent)
            self.emitter.emit(reply)
        elif len(utterances) == 1:
            self.emitter.emit(
                Message("intent_failure", {
                    "utterance": utterances[0],
                    "lang": lang
                }))
        else:
            self.emitter.emit(
                Message("multi_utterance_intent_failure", {
                    "utterances": utterances,
                    "lang": lang
                }))
class IntentService(object):
    def __init__(self, emitter):
        self.engine = IntentDeterminationEngine()
        self.emitter = emitter
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)
        self.emitter.on('detach_skill', self.handle_detach_skill)

    def handle_utterance(self, message):
        # Get language of the utterance
        lang = message.data.get('lang', None)
        if not lang:
            lang = "en-us"

        utterances = message.data.get('utterances', '')

        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(self.engine.determine_intent(
                    normalize(utterance, lang), 100))

                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration, e:
                logger.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            reply = message.reply(
                best_intent.get('intent_type'), best_intent)
            self.emitter.emit(reply)
        elif len(utterances) == 1:
            self.emitter.emit(Message("intent_failure", {
                "utterance": utterances[0],
                "lang": lang
            }))
        else:
            self.emitter.emit(Message("multi_utterance_intent_failure", {
                "utterances": utterances,
                "lang": lang
            }))
Exemplo n.º 15
0
def put_in_engin(text):
    engine = IntentDeterminationEngine()

    # Register entities on engine
    for entity, keywords in entities.items():
        for keyword in keywords:
            engine.register_entity(keyword, entity)

    for entity in multi_regex_entities:
        for regex in entity:
            engine.register_regex_entity(regex)

    # Register intents on engine
    for intent in intents:
        engine.register_intent_parser(intent)

    for intent in engine.determine_intent(text):
        # return dict[intenct] # will return function
        print(intent)

    print('finished')
Exemplo n.º 16
0
class WeatherIntentParser():
    def __init__(self):
        self.engine = IntentDeterminationEngine()
        weather_verbs = ["weather", "temperature", "forecast"]

        for mv in weather_verbs:
            self.engine.register_entity(mv, "WeatherVerb")

        self.engine.register_regex_entity(
            "in\s*(?P<Location>[A-Z][^\s]*\s*?)+.*$"  # NoQA
        )

        weather_intent = IntentBuilder("WeatherIntent")\
            .require("WeatherVerb")\
            .require("Location")\
            .build()

        self.engine.register_intent_parser(weather_intent)

    def parse(self, sentence):
        for intent in self.engine.determine_intent(sentence):
            if intent.get('confidence') > 0:
                return intent
Exemplo n.º 17
0
def get_intent(message):
    engine = IntentDeterminationEngine()

    keywords = [
        'service',
        'med',
        'clinic',
        'walk in',
    ]

    for key in keywords:
        engine.register_entity(key, "KeyWords")
    print(os.getcwd())
    with open(os.getcwd() + '/home/addresses.csv', 'rb') as csvfile:
        records = csv.reader(csvfile, delimiter=',')
        street_number = []
        street_name = []

        for row in records:
            street_number.append(row[8])
            street_name.append(row[11])

    for key in street_number:
        engine.register_entity(key, "StreetNumber")

    for key in street_name:
        engine.register_entity(key, "StreetName")

    address_intent = IntentBuilder("AddressIntent")\
        .require("KeyWords")\
        .optionally("StreetNumber")\
        .optionally("StreetName")\
        .build()

    engine.register_intent_parser(address_intent)
    for intent in engine.determine_intent(''.join(message)):
        return intent
Exemplo n.º 18
0
class IntentService(object):
    def __init__(self, emitter):
        self.config = Configuration.get().get('context', {})
        self.engine = IntentDeterminationEngine()

        # Dictionary for translating a skill id to a name
        self.skill_names = {}
        # Context related intializations
        self.context_keywords = self.config.get('keywords', [])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)
        self.emitter = emitter
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)
        self.emitter.on('detach_skill', self.handle_detach_skill)
        # Context related handlers
        self.emitter.on('add_context', self.handle_add_context)
        self.emitter.on('remove_context', self.handle_remove_context)
        self.emitter.on('clear_context', self.handle_clear_context)
        # Converse method
        self.emitter.on('skill.converse.response',
                        self.handle_converse_response)
        self.emitter.on('mycroft.speech.recognition.unknown',
                        self.reset_converse)
        self.emitter.on('mycroft.skills.loaded', self.update_skill_name_dict)

        def add_active_skill_handler(message):
            self.add_active_skill(message.data['skill_id'])
        self.emitter.on('active_skill_request', add_active_skill_handler)
        self.active_skills = []  # [skill_id , timestamp]
        self.converse_timeout = 5  # minutes to prune active_skills

    def update_skill_name_dict(self, message):
        """
            Messagebus handler, updates dictionary of if to skill name
            conversions.
        """
        self.skill_names[message.data['id']] = message.data['name']

    def get_skill_name(self, skill_id):
        """
            Get skill name from skill ID.

            Args
                skill_id: a skill id as encoded in Intent handlers.

            Returns: (str) Skill name or the skill id if the skill
                     wasn't found in the dict.
        """
        return self.skill_names.get(int(skill_id), skill_id)

    def reset_converse(self, message):
        """Let skills know there was a problem with speech recognition"""
        lang = message.data.get('lang', "en-us")
        for skill in self.active_skills:
            self.do_converse(None, skill[0], lang)

    def do_converse(self, utterances, skill_id, lang):
        self.emitter.emit(Message("skill.converse.request", {
            "skill_id": skill_id, "utterances": utterances, "lang": lang}))
        self.waiting = True
        self.result = False
        start_time = time.time()
        t = 0
        while self.waiting and t < 5:
            t = time.time() - start_time
            time.sleep(0.1)
        self.waiting = False
        return self.result

    def handle_converse_response(self, message):
        # id = message.data["skill_id"]
        # no need to crosscheck id because waiting before new request is made
        # no other skill will make this request is safe assumption
        result = message.data["result"]
        self.result = result
        self.waiting = False

    def remove_active_skill(self, skill_id):
        for skill in self.active_skills:
            if skill[0] == skill_id:
                self.active_skills.remove(skill)

    def add_active_skill(self, skill_id):
        # search the list for an existing entry that already contains it
        # and remove that reference
        self.remove_active_skill(skill_id)
        # add skill with timestamp to start of skill_list
        self.active_skills.insert(0, [skill_id, time.time()])

    def update_context(self, intent):
        """
            updates context with keyword from the intent.

            NOTE: This method currently won't handle one_of intent keywords
                  since it's not using quite the same format as other intent
                  keywords. This is under investigation in adapt, PR pending.

            Args:
                intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def send_metrics(self, intent, context, stopwatch):
        """
            Send timing metrics to the backend.
        """
        LOG.debug('Sending metric')
        ident = context['ident'] if context else None
        if intent:
            # Recreate skill name from skill id
            parts = intent.get('intent_type', '').split(':')
            intent_type = self.get_skill_name(parts[0])
            if len(parts) > 1:
                intent_type = ':'.join([intent_type] + parts[1:])
            report_timing(ident, 'intent_service', stopwatch,
                          {'intent_type': intent_type})
        else:
            report_timing(ident, 'intent_service', stopwatch,
                          {'intent_type': 'intent_failure'})

    def handle_utterance(self, message):
        """
            Messagebus handler for the recognizer_loop:utterance message
        """
        try:
            # Get language of the utterance
            lang = message.data.get('lang', "en-us")
            utterances = message.data.get('utterances', '')

            stopwatch = Stopwatch()
            with stopwatch:
                # Parse the sentence
                converse = self.parse_converse(utterances, lang)
                if not converse:
                    # no skill wants to handle utterance
                    intent = self.parse_utterances(utterances, lang)

            if converse:
                # Report that converse handled the intent and return
                ident = message.context['ident'] if message.context else None
                report_timing(ident, 'intent_service', stopwatch,
                              {'intent_type': 'converse'})
                return
            elif intent:
                # Send the message on to the intent handler
                reply = message.reply(intent.get('intent_type'), intent)
            else:
                # or if no match send sentence to fallback system
                reply = message.reply('intent_failure',
                                      {'utterance': utterances[0],
                                       'lang': lang})
            self.emitter.emit(reply)
            self.send_metrics(intent, message.context, stopwatch)
        except Exception as e:
            LOG.exception(e)

    def parse_converse(self, utterances, lang):
        """
            Converse, check if a recently invoked skill wants to
            handle the utterance and override normal adapt handling.

            Returns: True if converse handled the utterance, else False.
        """

        # check for conversation time-out
        self.active_skills = [skill for skill in self.active_skills
                              if time.time() - skill[
                                  1] <= self.converse_timeout * 60]

        # check if any skill wants to handle utterance
        for skill in self.active_skills:
            if self.do_converse(utterances, skill[0], lang):
                # update timestamp, or there will be a timeout where
                # intent stops conversing whether its being used or not
                self.add_active_skill(skill[0])
                return True
        return False

    def parse_utterances(self, utterances, lang):
        """
            Parse the utteracne using adapt  to find a matching intent.

            Args:
                utterances (list):  list of utterances
                lang (string):      4 letter ISO language code

            Returns: Intent structure, or None if no match was found.
        """
        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(self.engine.determine_intent(
                    normalize(utterance, lang), 100,
                    include_tags=True,
                    context_manager=self.context_manager))
                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration:
                # don't show error in log
                continue
            except Exception as e:
                LOG.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            self.update_context(best_intent)
            # update active skills
            skill_id = int(best_intent['intent_type'].split(":")[0])
            self.add_active_skill(skill_id)
            return best_intent

    def handle_register_vocab(self, message):
        start_concept = message.data.get('start')
        end_concept = message.data.get('end')
        regex_str = message.data.get('regex')
        alias_of = message.data.get('alias_of')
        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(
                start_concept, end_concept, alias_of=alias_of)

    def handle_register_intent(self, message):
        intent = open_intent_envelope(message)
        self.engine.register_intent_parser(intent)

    def handle_detach_intent(self, message):
        intent_name = message.data.get('intent_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name]
        self.engine.intent_parsers = new_parsers

    def handle_detach_skill(self, message):
        skill_id = message.data.get('skill_id')
        new_parsers = [
            p for p in self.engine.intent_parsers if
            not p.name.startswith(skill_id)]
        self.engine.intent_parsers = new_parsers

    def handle_add_context(self, message):
        """
            Handles adding context from the message bus.
            The data field must contain a context keyword and
            may contain a word if a specific word should be injected
            as a match for the provided context keyword.

        """
        entity = {'confidence': 1.0}
        context = message.data.get('context')
        word = message.data.get('word') or ''
        # if not a string type try creating a string from it
        if not isinstance(word, basestring):
            word = str(word)
        entity['data'] = [(word, context)]
        entity['match'] = word
        entity['key'] = word
        self.context_manager.inject_context(entity)

    def handle_remove_context(self, message):
        """
            Handles removing context from the message bus. The
            data field must contain the 'context' to remove.
        """
        context = message.data.get('context')
        if context:
            self.context_manager.remove_context(context)

    def handle_clear_context(self, message):
        """
            Clears all keywords from context.
        """
        self.context_manager.clear_context()
Exemplo n.º 19
0
class IntentService(object):
    def __init__(self, emitter):
        self.engine = IntentDeterminationEngine()
        self.emitter = emitter
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)
        self.emitter.on('detach_skill', self.handle_detach_skill)

    def handle_utterance(self, message):
        # Get language of the utterance
        lang = message.data.get('lang', None)
        if not lang:
            lang = "en-us"

        utterances = message.data.get('utterances', '')

        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(self.engine.determine_intent(
                    normalize(utterance, lang), 100))

                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration as e:
                logger.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            reply = message.reply(
                best_intent.get('intent_type'), best_intent)
            self.emitter.emit(reply)
        elif len(utterances) == 1:
            self.emitter.emit(Message("intent_failure", {
                "utterance": utterances[0],
                "lang": lang
            }))
        else:
            self.emitter.emit(Message("multi_utterance_intent_failure", {
                "utterances": utterances,
                "lang": lang
            }))

    def handle_register_vocab(self, message):
        start_concept = message.data.get('start')
        end_concept = message.data.get('end')
        regex_str = message.data.get('regex')
        alias_of = message.data.get('alias_of')
        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(
                start_concept, end_concept, alias_of=alias_of)

    def handle_register_intent(self, message):
        intent = open_intent_envelope(message)
        self.engine.register_intent_parser(intent)

    def handle_detach_intent(self, message):
        intent_name = message.data.get('intent_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name]
        self.engine.intent_parsers = new_parsers

    def handle_detach_skill(self, message):
        skill_name = message.data.get('skill_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if
            not p.name.startswith(skill_name)]
        self.engine.intent_parsers = new_parsers
Exemplo n.º 20
0
class IntentService(object):
    def __init__(self, emitter):
        self.config = Configuration.get().get('context', {})
        self.engine = IntentDeterminationEngine()
        self.context_keywords = self.config.get('keywords', [])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)
        self.emitter = emitter
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)
        self.emitter.on('detach_skill', self.handle_detach_skill)
        # Context related handlers
        self.emitter.on('add_context', self.handle_add_context)
        self.emitter.on('remove_context', self.handle_remove_context)
        self.emitter.on('clear_context', self.handle_clear_context)
        # Converse method
        self.emitter.on('skill.converse.response',
                        self.handle_converse_response)
        self.emitter.on('mycroft.speech.recognition.unknown',
                        self.reset_converse)

        def add_active_skill_handler(message):
            self.add_active_skill(message.data['skill_id'])
        self.emitter.on('active_skill_request', add_active_skill_handler)
        self.active_skills = []  # [skill_id , timestamp]
        self.converse_timeout = 5  # minutes to prune active_skills

    def reset_converse(self, message):
        """Let skills know there was a problem with speech recognition"""
        lang = message.data.get('lang', "en-us")
        for skill in self.active_skills:
            self.do_converse(None, skill[0], lang)

    def do_converse(self, utterances, skill_id, lang):
        self.emitter.emit(Message("skill.converse.request", {
            "skill_id": skill_id, "utterances": utterances, "lang": lang}))
        self.waiting = True
        self.result = False
        start_time = time.time()
        t = 0
        while self.waiting and t < 5:
            t = time.time() - start_time
            time.sleep(0.1)
        self.waiting = False
        return self.result

    def handle_converse_response(self, message):
        # id = message.data["skill_id"]
        # no need to crosscheck id because waiting before new request is made
        # no other skill will make this request is safe assumption
        result = message.data["result"]
        self.result = result
        self.waiting = False

    def remove_active_skill(self, skill_id):
        for skill in self.active_skills:
            if skill[0] == skill_id:
                self.active_skills.remove(skill)

    def add_active_skill(self, skill_id):
        # search the list for an existing entry that already contains it
        # and remove that reference
        self.remove_active_skill(skill_id)
        # add skill with timestamp to start of skill_list
        self.active_skills.insert(0, [skill_id, time.time()])

    def update_context(self, intent):
        """
            updates context with keyword from the intent.

            NOTE: This method currently won't handle one_of intent keywords
                  since it's not using quite the same format as other intent
                  keywords. This is under investigation in adapt, PR pending.

            Args:
                intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def handle_utterance(self, message):
        # Get language of the utterance
        lang = message.data.get('lang', "en-us")

        utterances = message.data.get('utterances', '')

        # check for conversation time-out
        self.active_skills = [skill for skill in self.active_skills
                              if time.time() - skill[
                                  1] <= self.converse_timeout * 60]

        # check if any skill wants to handle utterance
        for skill in self.active_skills:
            if self.do_converse(utterances, skill[0], lang):
                # update timestamp, or there will be a timeout where
                # intent stops conversing whether its being used or not
                self.add_active_skill(skill[0])
                return

        # no skill wants to handle utterance
        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(self.engine.determine_intent(
                    normalize(utterance, lang), 100,
                    include_tags=True,
                    context_manager=self.context_manager))
                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration:
                # don't show error in log
                continue
            except e:
                LOG.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            self.update_context(best_intent)
            reply = message.reply(
                best_intent.get('intent_type'), best_intent)
            self.emitter.emit(reply)
            # update active skills
            skill_id = int(best_intent['intent_type'].split(":")[0])
            self.add_active_skill(skill_id)

        else:
            self.emitter.emit(Message("intent_failure", {
                "utterance": utterances[0],
                "lang": lang
            }))

    def handle_register_vocab(self, message):
        start_concept = message.data.get('start')
        end_concept = message.data.get('end')
        regex_str = message.data.get('regex')
        alias_of = message.data.get('alias_of')
        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(
                start_concept, end_concept, alias_of=alias_of)

    def handle_register_intent(self, message):
        print "Registering: " + str(message.data)
        intent = open_intent_envelope(message)
        self.engine.register_intent_parser(intent)

    def handle_detach_intent(self, message):
        intent_name = message.data.get('intent_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name]
        self.engine.intent_parsers = new_parsers

    def handle_detach_skill(self, message):
        skill_id = message.data.get('skill_id')
        new_parsers = [
            p for p in self.engine.intent_parsers if
            not p.name.startswith(skill_id)]
        self.engine.intent_parsers = new_parsers

    def handle_add_context(self, message):
        """
            Handles adding context from the message bus.
            The data field must contain a context keyword and
            may contain a word if a specific word should be injected
            as a match for the provided context keyword.

        """
        entity = {'confidence': 1.0}
        context = message.data.get('context')
        word = message.data.get('word') or ''
        # if not a string type try creating a string from it
        if not isinstance(word, basestring):
            word = str(word)
        entity['data'] = [(word, context)]
        entity['match'] = word
        entity['key'] = word
        self.context_manager.inject_context(entity)

    def handle_remove_context(self, message):
        """
            Handles removing context from the message bus. The
            data field must contain the 'context' to remove.
        """
        context = message.data.get('context')
        if context:
            self.context_manager.remove_context(context)

    def handle_clear_context(self, message):
        """
            Clears all keywords from context.
        """
        self.context_manager.clear_context()
Exemplo n.º 21
0
class AdaptService:
    """Intent service wrapping the Apdapt intent Parser."""
    def __init__(self, config):
        self.config = config
        self.engine = IntentDeterminationEngine()
        # Context related intializations
        self.context_keywords = self.config.get('keywords', [])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)

    def update_context(self, intent):
        """Updates context with keyword from the intent.

        NOTE: This method currently won't handle one_of intent keywords
              since it's not using quite the same format as other intent
              keywords. This is under investigation in adapt, PR pending.

        Args:
            intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def match_intent(self, utterances, _=None, __=None):
        """Run the Adapt engine to search for an matching intent.

        Arguments:
            utterances (iterable): iterable of utterances, expected order
                                   [raw, normalized, other]

        Returns:
            Intent structure, or None if no match was found.
        """
        best_intent = {}

        def take_best(intent, utt):
            nonlocal best_intent
            best = best_intent.get('confidence', 0.0) if best_intent else 0.0
            conf = intent.get('confidence', 0.0)
            if conf > best:
                best_intent = intent
                # TODO - Shouldn't Adapt do this?
                best_intent['utterance'] = utt

        for utt_tup in utterances:
            for utt in utt_tup:
                try:
                    intents = [
                        i for i in self.engine.determine_intent(
                            utt,
                            100,
                            include_tags=True,
                            context_manager=self.context_manager)
                    ]
                    if intents:
                        take_best(intents[0], utt_tup[0])

                except Exception as err:
                    LOG.exception(err)

        if best_intent:
            self.update_context(best_intent)
            skill_id = best_intent['intent_type'].split(":")[0]
            ret = IntentMatch('Adapt', best_intent['intent_type'], best_intent,
                              skill_id)
        else:
            ret = None
        return ret

    def register_vocab(self, start_concept, end_concept, alias_of, regex_str):
        """Register vocabulary."""
        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(start_concept,
                                        end_concept,
                                        alias_of=alias_of)

    def register_intent(self, intent):
        """Register new intent with adapt engine.

        Arguments:
            intent (IntentParser): IntentParser to register
        """
        self.engine.register_intent_parser(intent)

    def detach_skill(self, skill_id):
        """Remove all intents for skill.

        Arguments:
            skill_id (str): skill to process
        """
        new_parsers = [
            p for p in self.engine.intent_parsers
            if not p.name.startswith(skill_id)
        ]
        self.engine.intent_parsers = new_parsers

    def detach_intent(self, intent_name):
        """Detatch a single intent

        Arguments:
            intent_name (str): Identifier for intent to remove.
        """
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name
        ]
        self.engine.intent_parsers = new_parsers
Exemplo n.º 22
0
class AdaptExtractor(IntentExtractor):
    keyword_based = True
    regex_entity_support = True

    def __init__(self, normalize=False, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.normalize = normalize
        self.engine = IntentDeterminationEngine()

    def register_entity(self, entity_name, samples=None):
        samples = samples or [entity_name]
        for kw in samples:
            self.engine.register_entity(kw, entity_name)
        super().register_entity(entity_name, samples)

    def register_regex_entity(self, entity_name, samples):
        if isinstance(samples, str):
            self.engine.register_regex_entity(samples)
        if isinstance(samples, list):
            for s in samples:
                self.engine.register_regex_entity(s)

    def register_regex_intent(self, intent_name, samples):
        self.register_regex_entity(intent_name + "_adapt_rx", samples)
        self.register_intent(intent_name, [intent_name + "_adapt_rx"])

    def register_intent(self, intent_name, samples=None,
                        optional_samples=None, rx_samples=None):
        """

        :param intent_name: intent_name
        :param samples: list of required registered entities (names)
        :param optional_samples: list of optional registered samples (names)
        :return:
        """
        super().register_entity(intent_name, samples)
        if not samples:
            samples = [intent_name]
            self.register_entity(intent_name, samples)
        optional_samples = optional_samples or []

        # structure intent
        intent = IntentBuilder(intent_name)
        for kw in samples:
            intent.require(kw)
        for kw in optional_samples:
            intent.optionally(kw)
        self.engine.register_intent_parser(intent.build())
        return intent

    def calc_intent(self, utterance):
        utterance = utterance.strip()
        if self.normalize:
            utterance = normalize(utterance, self.lang, True)
        for intent in self.engine.determine_intent(utterance, 100,
                                                   include_tags=True,
                                                   context_manager=self.context_manager):
            if intent and intent.get('confidence') > 0:
                intent.pop("target")
                matches = {k: v for k, v in intent.items() if
                           k not in ["intent_type", "confidence", "__tags__"]}
                intent["entities"] = {}
                for k in matches:
                    intent["entities"][k] = intent.pop(k)
                intent["conf"] = intent.pop("confidence")
                intent["utterance"] = utterance
                intent["intent_engine"] = "adapt"

                remainder = get_utterance_remainder(
                    utterance, samples=[v for v in matches.values()])
                intent["utterance_remainder"] = remainder
                return intent
        return {"conf": 0, "intent_type": "unknown", "entities": {},
                "utterance_remainder": utterance,
                "utterance": utterance, "intent_engine": "adapt"}

    def calc_intents(self, utterance, min_conf=0.5):
        bucket = {}
        for ut in self.segmenter.segment(utterance):
            intent = self.calc_intent(ut)
            if intent["conf"] < min_conf:
                bucket[ut] = None
            else:
                bucket[ut] = intent
        return bucket

    def calc_intents_list(self, utterance, min_conf=0.5):
        utterance = utterance.strip()  # spaces should not mess with exact matches
        bucket = {}
        for ut in self.segmenter.segment(utterance):

            if self.normalize:
                ut = normalize(ut, self.lang, True)
            bucket[ut] = []
            for intent in self.engine.determine_intent(ut, 100,
                                                       include_tags=True,
                                                       context_manager=self.context_manager):
                if intent:
                    intent.pop("target")
                    matches = {k: v for k, v in intent.items() if
                               k not in ["intent_type", "confidence",
                                         "__tags__"]}
                    intent["entities"] = {}
                    for k in matches:
                        intent["entities"][k] = intent.pop(k)
                    intent["conf"] = intent.pop("confidence")
                    intent["utterance"] = ut
                    intent["intent_engine"] = "adapt"
                    remainder = get_utterance_remainder(
                        utterance, samples=[v for v in matches.values()])
                    intent["utterance_remainder"] = remainder
                    if intent["conf"] >= min_conf:
                        bucket[ut] += [intent]

        return bucket

    def intent_scores(self, utterance):
        utterance = utterance.strip()  # spaces should not mess with exact matches
        bucket = []
        for intent in self.engine.determine_intent(utterance, 100,
                                                   include_tags=True,
                                                   context_manager=self.context_manager):
            if intent:
                intent.pop("target")
                matches = {k: v for k, v in intent.items() if
                           k not in ["intent_type", "confidence", "__tags__"]}
                intent["entities"] = {}
                for k in matches:
                    intent["entities"][k] = intent.pop(k)
                intent["conf"] = intent.pop("confidence")
                intent["intent_engine"] = "adapt"
                intent["utterance"] = utterance

                remainder = get_utterance_remainder(
                    utterance, samples=[v for v in matches.values()])
                intent["utterance_remainder"] = remainder
                bucket += [intent]
        return bucket

    def intent_remainder(self, utterance, _prev=""):
        utterance = utterance.strip()  # spaces should not mess with exact matches
        if self.normalize:
            utterance = normalize(utterance, self.lang, True)
        return IntentExtractor.intent_remainder(self, utterance)

    def intents_remainder(self, utterance, min_conf=0.5):
        """
        segment utterance and for each chunk recursively check for intents in utterance remainer

        :param utterance:
        :param min_conf:
        :return:
        """
        utterance = utterance.strip()  # spaces should not mess with exact matches
        bucket = {}
        for utterance in self.segmenter.segment(utterance):
            if self.normalize:
                utterance = normalize(utterance, self.lang, True)
            bucket[utterance] = self.intent_remainder(utterance)
        return bucket

    def segment(self, text):
        if self.normalize:
            text = normalize(text, self.lang, True)
        return self.segment(text)

    def detach_intent(self, intent_name):
        LOG.debug("detaching adapt intent: " + intent_name)
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name]
        self.engine.intent_parsers = new_parsers

    def detach_skill(self, skill_id):
        LOG.debug("detaching adapt skill: " + skill_id)
        new_parsers = [
            p.name for p in self.engine.intent_parsers if
            p.name.startswith(skill_id)]
        for intent_name in new_parsers:
            self.detach_intent(intent_name)

    def manifest(self):
        # TODO vocab, skill ids, intent_data
        return {
            "intent_names": [p.name for p in self.engine.intent_parsers]
        }
Exemplo n.º 23
0
class IntentService:
    def __init__(self, bus):
        self.config = Configuration.get().get('context', {})
        self.engine = IntentDeterminationEngine()

        # Dictionary for translating a skill id to a name
        self.skill_names = {}
        # Context related intializations
        self.context_keywords = self.config.get('keywords', [])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)
        self.bus = bus
        self.bus.on('register_vocab', self.handle_register_vocab)
        self.bus.on('register_intent', self.handle_register_intent)
        self.bus.on('recognizer_loop:utterance', self.handle_utterance)
        self.bus.on('detach_intent', self.handle_detach_intent)
        self.bus.on('detach_skill', self.handle_detach_skill)
        # Context related handlers
        self.bus.on('add_context', self.handle_add_context)
        self.bus.on('remove_context', self.handle_remove_context)
        self.bus.on('clear_context', self.handle_clear_context)
        # Converse method
        self.bus.on('skill.converse.response', self.handle_converse_response)
        self.bus.on('skill.converse.error', self.handle_converse_error)
        self.bus.on('mycroft.speech.recognition.unknown', self.reset_converse)
        self.bus.on('mycroft.skills.loaded', self.update_skill_name_dict)

        def add_active_skill_handler(message):
            self.add_active_skill(message.data['skill_id'])

        self.bus.on('active_skill_request', add_active_skill_handler)
        self.active_skills = []  # [skill_id , timestamp]
        self.converse_timeout = 5  # minutes to prune active_skills
        self.waiting_for_converse = False
        self.converse_result = False
        self.converse_skill_id = ""

        # Intents API
        self.registered_intents = []
        self.registered_vocab = []
        self.bus.on('intent.service.adapt.get', self.handle_get_adapt)
        self.bus.on('intent.service.intent.get', self.handle_get_intent)
        self.bus.on('intent.service.skills.get', self.handle_get_skills)
        self.bus.on('intent.service.active_skills.get',
                    self.handle_get_active_skills)
        self.bus.on('intent.service.adapt.manifest.get', self.handle_manifest)
        self.bus.on('intent.service.adapt.vocab.manifest.get',
                    self.handle_vocab_manifest)

    def update_skill_name_dict(self, message):
        """
            Messagebus handler, updates dictionary of if to skill name
            conversions.
        """
        self.skill_names[message.data['id']] = message.data['name']

    def get_skill_name(self, skill_id):
        """ Get skill name from skill ID.

        Args:
            skill_id: a skill id as encoded in Intent handlers.

        Returns:
            (str) Skill name or the skill id if the skill wasn't found
        """
        return self.skill_names.get(skill_id, skill_id)

    def reset_converse(self, message):
        """Let skills know there was a problem with speech recognition"""
        lang = message.data.get('lang', "en-us")
        set_active_lang(lang)
        for skill in self.active_skills:
            self.do_converse(None, skill[0], lang)

    def do_converse(self, utterances, skill_id, lang, message):
        self.waiting_for_converse = True
        self.converse_result = False
        self.converse_skill_id = skill_id
        self.bus.emit(
            message.reply("skill.converse.request", {
                "skill_id": skill_id,
                "utterances": utterances,
                "lang": lang
            }))
        start_time = time.time()
        t = 0
        while self.waiting_for_converse and t < 5:
            t = time.time() - start_time
            time.sleep(0.1)
        self.waiting_for_converse = False
        self.converse_skill_id = ""
        return self.converse_result

    def handle_converse_error(self, message):
        skill_id = message.data["skill_id"]
        if message.data["error"] == "skill id does not exist":
            self.remove_active_skill(skill_id)
        if skill_id == self.converse_skill_id:
            self.converse_result = False
            self.waiting_for_converse = False

    def handle_converse_response(self, message):
        skill_id = message.data["skill_id"]
        if skill_id == self.converse_skill_id:
            self.converse_result = message.data.get("result", False)
            self.waiting_for_converse = False

    def remove_active_skill(self, skill_id):
        for skill in self.active_skills:
            if skill[0] == skill_id:
                self.active_skills.remove(skill)

    def add_active_skill(self, skill_id):
        # search the list for an existing entry that already contains it
        # and remove that reference
        self.remove_active_skill(skill_id)
        # add skill with timestamp to start of skill_list
        self.active_skills.insert(0, [skill_id, time.time()])

    def update_context(self, intent):
        """ Updates context with keyword from the intent.

        NOTE: This method currently won't handle one_of intent keywords
              since it's not using quite the same format as other intent
              keywords. This is under investigation in adapt, PR pending.

        Args:
            intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def send_metrics(self, intent, context, stopwatch):
        """
        Send timing metrics to the backend.

        NOTE: This only applies to those with Opt In.
        """
        ident = context['ident'] if 'ident' in context else None
        if intent:
            # Recreate skill name from skill id
            parts = intent.get('intent_type', '').split(':')
            intent_type = self.get_skill_name(parts[0])
            if len(parts) > 1:
                intent_type = ':'.join([intent_type] + parts[1:])
            report_timing(ident, 'intent_service', stopwatch,
                          {'intent_type': intent_type})
        else:
            report_timing(ident, 'intent_service', stopwatch,
                          {'intent_type': 'intent_failure'})

    def handle_utterance(self, message):
        """ Main entrypoint for handling user utterances with Mycroft skills

        Monitor the messagebus for 'recognizer_loop:utterance', typically
        generated by a spoken interaction but potentially also from a CLI
        or other method of injecting a 'user utterance' into the system.

        Utterances then work through this sequence to be handled:
        1) Active skills attempt to handle using converse()
        2) Padatious high match intents (conf > 0.95)
        3) Adapt intent handlers
        5) Fallbacks:
           - Padatious near match intents (conf > 0.8)
           - General fallbacks
           - Padatious loose match intents (conf > 0.5)
           - Unknown intent handler

        Args:
            message (Message): The messagebus data
        """
        try:
            # Get language of the utterance
            lang = message.data.get('lang', "en-us")
            set_active_lang(lang)

            utterances = message.data.get('utterances', [])
            # normalize() changes "it's a boy" to "it is a boy", etc.
            norm_utterances = [
                normalize(u.lower(), remove_articles=False) for u in utterances
            ]

            # Build list with raw utterance(s) first, then optionally a
            # normalized version following.
            combined = utterances + list(
                set(norm_utterances) - set(utterances))
            LOG.debug("Utterances: {}".format(combined))

            stopwatch = Stopwatch()
            intent = None
            padatious_intent = None
            with stopwatch:
                # Give active skills an opportunity to handle the utterance
                converse = self._converse(combined, lang, message)

                if not converse:
                    # No conversation, use intent system to handle utterance
                    intent = self._adapt_intent_match(utterances,
                                                      norm_utterances, lang)
                    for utt in combined:
                        _intent = PadatiousService.instance.calc_intent(utt)
                        if _intent:
                            best = padatious_intent.conf if padatious_intent \
                                else 0.0
                            if best < _intent.conf:
                                padatious_intent = _intent
                    LOG.debug("Padatious intent: {}".format(padatious_intent))
                    LOG.debug("    Adapt intent: {}".format(intent))

            if converse:
                # Report that converse handled the intent and return
                LOG.debug("Handled in converse()")
                ident = None
                if message.context and 'ident' in message.context:
                    ident = message.context['ident']
                report_timing(ident, 'intent_service', stopwatch,
                              {'intent_type': 'converse'})
                return
            elif (intent and intent.get('confidence', 0.0) > 0.0 and
                  not (padatious_intent and padatious_intent.conf >= 0.95)):
                # Send the message to the Adapt intent's handler unless
                # Padatious is REALLY sure it was directed at it instead.
                self.update_context(intent)
                # update active skills
                skill_id = intent['intent_type'].split(":")[0]
                self.add_active_skill(skill_id)
                # Adapt doesn't handle context injection for one_of keywords
                # correctly. Workaround this issue if possible.
                try:
                    intent = workaround_one_of_context(intent)
                except LookupError:
                    LOG.error('Error during workaround_one_of_context')
                reply = message.reply(intent.get('intent_type'), intent)
            else:
                # Allow fallback system to handle utterance
                # NOTE: A matched padatious_intent is handled this way, too
                # TODO: Need to redefine intent_failure when STT can return
                #       multiple hypothesis -- i.e. len(utterances) > 1
                reply = message.reply(
                    'intent_failure', {
                        'utterance': utterances[0],
                        'norm_utt': norm_utterances[0],
                        'lang': lang
                    })
            self.bus.emit(reply)
            self.send_metrics(intent, message.context, stopwatch)
        except Exception as e:
            LOG.exception(e)

    def _converse(self, utterances, lang, message):
        """ Give active skills a chance at the utterance

        Args:
            utterances (list):  list of utterances
            lang (string):      4 letter ISO language code
            message (Message):  message to use to generate reply

        Returns:
            bool: True if converse handled it, False if  no skill processes it
        """

        # check for conversation time-out
        self.active_skills = [
            skill for skill in self.active_skills
            if time.time() - skill[1] <= self.converse_timeout * 60
        ]

        # check if any skill wants to handle utterance
        for skill in self.active_skills:
            if self.do_converse(utterances, skill[0], lang, message):
                # update timestamp, or there will be a timeout where
                # intent stops conversing whether its being used or not
                self.add_active_skill(skill[0])
                return True
        return False

    def _adapt_intent_match(self, raw_utt, norm_utt, lang):
        """ Run the Adapt engine to search for an matching intent

        Args:
            raw_utt (list):  list of utterances
            norm_utt (list): same list of utterances, normalized
            lang (string):   language code, e.g "en-us"

        Returns:
            Intent structure, or None if no match was found.
        """
        best_intent = None

        def take_best(intent, utt):
            nonlocal best_intent
            best = best_intent.get('confidence', 0.0) if best_intent else 0.0
            conf = intent.get('confidence', 0.0)
            if conf > best:
                best_intent = intent
                # TODO - Shouldn't Adapt do this?
                best_intent['utterance'] = utt

        for idx, utt in enumerate(raw_utt):
            try:
                intents = [
                    i for i in self.engine.determine_intent(
                        utt,
                        100,
                        include_tags=True,
                        context_manager=self.context_manager)
                ]
                if intents:
                    take_best(intents[0], utt)

                # Also test the normalized version, but set the utterance to
                # the raw version so skill has access to original STT
                norm_intents = [
                    i for i in self.engine.determine_intent(
                        norm_utt[idx],
                        100,
                        include_tags=True,
                        context_manager=self.context_manager)
                ]
                if norm_intents:
                    take_best(norm_intents[0], utt)
            except Exception as e:
                LOG.exception(e)
        return best_intent

    def handle_register_vocab(self, message):
        start_concept = message.data.get('start')
        end_concept = message.data.get('end')
        regex_str = message.data.get('regex')
        alias_of = message.data.get('alias_of')
        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(start_concept,
                                        end_concept,
                                        alias_of=alias_of)
        self.registered_vocab.append(message.data)

    def handle_register_intent(self, message):
        intent = open_intent_envelope(message)
        self.engine.register_intent_parser(intent)

    def handle_detach_intent(self, message):
        intent_name = message.data.get('intent_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name
        ]
        self.engine.intent_parsers = new_parsers

    def handle_detach_skill(self, message):
        skill_id = message.data.get('skill_id')
        new_parsers = [
            p for p in self.engine.intent_parsers
            if not p.name.startswith(skill_id)
        ]
        self.engine.intent_parsers = new_parsers

    def handle_add_context(self, message):
        """ Add context

        Args:
            message: data contains the 'context' item to add
                     optionally can include 'word' to be injected as
                     an alias for the context item.
        """
        entity = {'confidence': 1.0}
        context = message.data.get('context')
        word = message.data.get('word') or ''
        origin = message.data.get('origin') or ''
        # if not a string type try creating a string from it
        if not isinstance(word, str):
            word = str(word)
        entity['data'] = [(word, context)]
        entity['match'] = word
        entity['key'] = word
        entity['origin'] = origin
        self.context_manager.inject_context(entity)

    def handle_remove_context(self, message):
        """ Remove specific context

        Args:
            message: data contains the 'context' item to remove
        """
        context = message.data.get('context')
        if context:
            self.context_manager.remove_context(context)

    def handle_clear_context(self, message):
        """ Clears all keywords from context """
        self.context_manager.clear_context()

    def handle_get_adapt(self, message):
        utterance = message.data["utterance"]
        lang = message.data.get("lang", "en-us")
        norm = normalize(utterance, lang, remove_articles=False)
        intent = self._adapt_intent_match([utterance], [norm], lang)
        self.bus.emit(
            message.reply("intent.service.adapt.reply", {"intent": intent}))

    def handle_get_intent(self, message):
        utterance = message.data["utterance"]
        lang = message.data.get("lang", "en-us")
        norm = normalize(utterance, lang, remove_articles=False)
        intent = self._adapt_intent_match([utterance], [norm], lang)
        # Adapt intent's handler is used unless
        # Padatious is REALLY sure it was directed at it instead.
        padatious_intent = PadatiousService.instance.calc_intent(utterance)
        if not padatious_intent and norm != utterance:
            padatious_intent = PadatiousService.instance.calc_intent(norm)
        if intent is None or (padatious_intent
                              and padatious_intent.conf >= 0.95):
            intent = padatious_intent.__dict__
        self.bus.emit(
            message.reply("intent.service.intent.reply", {"intent": intent}))

    def handle_get_skills(self, message):
        self.bus.emit(
            message.reply("intent.service.skills.reply",
                          {"skills": self.skill_names}))

    def handle_get_active_skills(self, message):
        self.bus.emit(
            message.reply("intent.service.active_skills.reply",
                          {"skills": [s[0] for s in self.active_skills]}))

    def handle_manifest(self, message):
        self.bus.emit(
            message.reply("intent.service.adapt.manifest",
                          {"intents": self.registered_intents}))

    def handle_vocab_manifest(self, message):
        self.bus.emit(
            message.reply("intent.service.adapt.vocab.manifest",
                          {"vocab": self.registered_vocab}))
Exemplo n.º 24
0
class ContextManagerIntegrationTest(unittest.TestCase):
    def setUp(self):
        self.context_manager = ContextManager()
        self.engine = IntentDeterminationEngine()

    def testBasicContextualFollowup(self):
        intent1 = IntentBuilder("TimeQueryIntent")\
            .require("TimeQuery")\
            .require("Location")\
            .build()
        intent2 = IntentBuilder("WeatherQueryIntent")\
            .require("WeatherKeyword")\
            .require("Location")\
            .build()

        self.engine.register_intent_parser(intent1)
        self.engine.register_intent_parser(intent2)

        self.engine.register_entity("what time is it", "TimeQuery")
        self.engine.register_entity("seattle", "Location")
        self.engine.register_entity("miami", "Location")

        self.engine.register_entity("weather", "WeatherKeyword")

        utterance1 = "what time is it in seattle"
        intent = next(
            self.engine.determine_intent(utterance1,
                                         include_tags=True,
                                         context_manager=self.context_manager))
        assert intent
        assert intent['intent_type'] == 'TimeQueryIntent'
        assert '__tags__' in intent
        for tag in intent['__tags__']:
            context_entity = tag.get('entities')[0]
            self.context_manager.inject_context(context_entity)

        utterance2 = "what's the weather like?"
        intent = next(
            self.engine.determine_intent(utterance2,
                                         context_manager=self.context_manager))
        assert intent
        assert intent['intent_type'] == 'WeatherQueryIntent'

    def testContextOnlyUsedOnce(self):
        intent_parser = IntentBuilder("DummyIntent")\
            .require("Foo")\
            .optionally("Foo", "Foo2")\
            .build()

        context_entity = {
            'confidence': 1.0,
            'data': [('foo', 'Foo')],
            'match': 'foo',
            'key': 'foo'
        }
        self.context_manager.inject_context(context_entity)
        self.engine.register_intent_parser(intent_parser)
        self.engine.register_entity("foo", "Foo")
        self.engine.register_entity("fop", "Foo")

        intent = next(
            self.engine.determine_intent("foo",
                                         include_tags=True,
                                         context_manager=self.context_manager))
        assert intent
        assert intent['intent_type'] == "DummyIntent"
        assert not (intent.get("Foo") and intent.get("Foo2"))

    def testContextAndOneOf(self):
        # test to cover https://github.com/MycroftAI/adapt/issues/86
        engine = IntentDeterminationEngine()
        context_manager = ContextManager()

        # define vocabulary
        weather_keyword = ["weather"]

        for wk in weather_keyword:
            engine.register_entity(wk, "WeatherKeyword")

        # structure intent
        weather_intent = IntentBuilder("WeatherIntent") \
            .require("WeatherKeyword") \
            .one_of("Location", "LocationContext").build()

        engine.register_intent_parser(weather_intent)
        word = 'lizard'
        context = 'LocationContext'
        entity = {}
        entity['data'] = [(word, context)]
        entity['match'] = word
        entity['key'] = word
        context_manager.inject_context(entity)

        intents = list(
            engine.determine_intent('weather',
                                    context_manager=context_manager))
        self.assertEqual(1, len(intents), "Incorrect number of intents")
        result = intents[0]
        self.assertEqual("lizard", result.get("LocationContext"),
                         "Context not matched")
        self.assertEqual(0.75, result.get('confidence'),
                         "Context confidence not properly applied.")
Exemplo n.º 25
0
    if text.startswith("b'update"):
        engine = IntentDeterminationEngine()

        DOMTree = parse('config.xml')
        for intent in DOMTree.getElementsByTagName("intent"):
            builder = IntentBuilder(intent.attributes["name"].value)
            for node in intent.getElementsByTagName("keyword"):
                keyword = node.attributes["name"].value
                required = node.attributes["required"].value
                ktype = node.attributes["type"].value
                if ktype == 'normal':
                    for child in node.getElementsByTagName("item"):
                        engine.register_entity(child.childNodes[0].nodeValue,
                                               keyword)
                else:
                    engine.register_regex_entity(node.childNodes[0].nodeValue)
                if required == 'true':
                    builder.require(keyword)
                else:
                    builder.optionally(keyword)
            engine.register_intent_parser(builder.build())
    else:
        for intent in engine.determine_intent(" " + text):
            if intent.get('confidence') > 0:
                client.send(
                    str(json.dumps(intent, indent=4)).encode(encoding='utf_8',
                                                             errors='strict'))
        client.close()

server.close()
Exemplo n.º 26
0
class AdaptService:
    """Intent service wrapping the Apdapt intent Parser."""
    def __init__(self, config):
        self.config = config
        self.engine = IntentDeterminationEngine()
        # Context related intializations
        self.context_keywords = self.config.get('keywords', [])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)
        self.lock = Lock()

    def update_context(self, intent):
        """Updates context with keyword from the intent.

        NOTE: This method currently won't handle one_of intent keywords
              since it's not using quite the same format as other intent
              keywords. This is under investigation in adapt, PR pending.

        Args:
            intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def match_intent(self, utterances, _=None, __=None):
        """Run the Adapt engine to search for an matching intent.

        Args:
            utterances (iterable): utterances for consideration in intent
            matching. As a practical matter, a single utterance will be
            passed in most cases.  But there are instances, such as
            streaming STT that could pass multiple.  Each utterance
            is represented as a tuple containing the raw, normalized, and
            possibly other variations of the utterance.

        Returns:
            Intent structure, or None if no match was found.
        """
        best_intent = {}

        def take_best(intent, utt):
            nonlocal best_intent
            best = best_intent.get('confidence', 0.0) if best_intent else 0.0
            conf = intent.get('confidence', 0.0)
            if conf > best:
                best_intent = intent
                # TODO - Shouldn't Adapt do this?
                best_intent['utterance'] = utt

        for utt_tup in utterances:
            for utt in utt_tup:
                try:
                    intents = [
                        i for i in self.engine.determine_intent(
                            utt,
                            100,
                            include_tags=True,
                            context_manager=self.context_manager)
                    ]
                    if intents:
                        utt_best = max(intents,
                                       key=lambda x: x.get('confidence', 0.0))
                        take_best(utt_best, utt_tup[0])

                except Exception as err:
                    LOG.exception(err)

        if best_intent:
            self.update_context(best_intent)
            skill_id = best_intent['intent_type'].split(":")[0]
            ret = IntentMatch('Adapt', best_intent['intent_type'], best_intent,
                              skill_id)
        else:
            ret = None
        return ret

    # TODO 22.02: Remove this deprecated method
    def register_vocab(self, start_concept, end_concept, alias_of, regex_str):
        """Register Vocabulary. DEPRECATED

        This method should not be used, it has been replaced by
        register_vocabulary().
        """
        self.register_vocabulary(start_concept, end_concept, alias_of,
                                 regex_str)

    def register_vocabulary(self, entity_value, entity_type, alias_of,
                            regex_str):
        """Register skill vocabulary as adapt entity.

        This will handle both regex registration and registration of normal
        keywords. if the "regex_str" argument is set all other arguments will
        be ignored.

        Argument:
            entity_value: the natural langauge word
            entity_type: the type/tag of an entity instance
            alias_of: entity this is an alternative for
        """
        with self.lock:
            if regex_str:
                self.engine.register_regex_entity(regex_str)
            else:
                self.engine.register_entity(entity_value,
                                            entity_type,
                                            alias_of=alias_of)

    def register_intent(self, intent):
        """Register new intent with adapt engine.

        Args:
            intent (IntentParser): IntentParser to register
        """
        with self.lock:
            self.engine.register_intent_parser(intent)

    def detach_skill(self, skill_id):
        """Remove all intents for skill.

        Args:
            skill_id (str): skill to process
        """
        with self.lock:
            skill_parsers = [
                p.name for p in self.engine.intent_parsers
                if p.name.startswith(skill_id)
            ]
            self.engine.drop_intent_parser(skill_parsers)
            self._detach_skill_keywords(skill_id)
            self._detach_skill_regexes(skill_id)

    def _detach_skill_keywords(self, skill_id):
        """Detach all keywords registered with a particular skill.

        Arguments:
            skill_id (str): skill identifier
        """
        skill_id = _entity_skill_id(skill_id)

        def match_skill_entities(data):
            return data and data[1].startswith(skill_id)

        self.engine.drop_entity(match_func=match_skill_entities)

    def _detach_skill_regexes(self, skill_id):
        """Detach all regexes registered with a particular skill.

        Arguments:
            skill_id (str): skill identifier
        """
        skill_id = _entity_skill_id(skill_id)

        def match_skill_regexes(regexp):
            return any(
                [r.startswith(skill_id) for r in regexp.groupindex.keys()])

        self.engine.drop_regex_entity(match_func=match_skill_regexes)

    def detach_intent(self, intent_name):
        """Detatch a single intent

        Args:
            intent_name (str): Identifier for intent to remove.
        """
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name
        ]
        self.engine.intent_parsers = new_parsers
Exemplo n.º 27
0
# Register entities in engine
for entity, keywords in entities.items():
    for keyword in keywords:
        engine.register_entity(keyword, entity)

for entity in multi_regex_entities:
    for regex in entity:
        engine.register_regex_entity(regex)

# Register intents on engine
for intent in intents:
    engine.register_intent_parser(intent)

text1 = 'what is the number of active jobs today?'
text2 = 'create assignment buy mic for john'

for intent in engine.determine_intent(text1):
    print(intent)

for intent in engine.determine_intent(text2):
    print(intent)


def f1():
    print(' i am connecting todo')


h1 = Handler.Handler(intents[0], f1)

h1.compute()  #
Exemplo n.º 28
0
        self.intent = 'courtesyIntent'

        # define vocabulary
        self.courtesy_keyword = [
                "bonjour"
                ]

        # structure intent
        self.courtesy_intent = IntentBuilder(self.intent)\
                .require("CourtesyKeyword")\
                .build()

    def register(self, engine):
        for wk in self.courtesy_keyword:
            engine.register_entity(wk, "courtesyKeyword")
        engine.register_intent_parser(self.courtesy_intent)

    def process(self, json):
         result = sympy.sympify(json.get('CourtesyKeyword'))
         if json.get('CourtesyKeyword') == "bonjour":
             return "Bonjour monsieur"

if __name__ == "__main__":
    engine = IntentDeterminationEngine()
    skill = courtesySkill()
    skill.register(engine)
    for intent in engine.determine_intent(' '.join(sys.argv[1:])):
        if intent and intent.get('confidence') > 0:
            print(json.dumps(intent, indent=4))
Exemplo n.º 29
0
class CrmnextChatBot:
    def __init__(self):
        """
        Init for logger Intent engine Corpus
        """
        self.engine = IntentDeterminationEngine()
        self.bot = BotLogger()
        self.cr = BotNextCorpus()
        self.first_time = True
        self.intent = []
        self.train_data = {'intent': "", 'user_text': "", 'bot_response': ""}
        self.train_intent()
        self.senti = SentimentIntensityAnalyzer()

    def train_intent(self):
        """
        Training intent engine to classify text intent
        :return:
        """
        for k in self.cr.train_intent_list.keys():
            for w in self.cr.train_intent_list[k]:
                self.engine.register_entity(w, k)

        for w in self.cr.train_intent.keys():
            self.intent.append(IntentBuilder(w) \
                    .require(self.cr.train_intent[w]) \
                    .build())
        for obj in self.intent:
            self.engine.register_intent_parser(obj)

    def run_bot(self, conv):
        """
        Thread starts from here.
        :return:
        """
        self.bot.log_debug("Received Json in run_bot: {}".format(conv))
        msg = conv["user_text"]
        isFirst = True if conv["intent_type"] == "" else False

        if self.sent(msg):
            if isFirst:
                try:
                    intent = json.loads(self.intent_parser(msg))
                except Exception:
                    intent = {
                        "intent_type": "",
                        "Card": "",
                        "CardLost": "",
                        "target": "",
                        "confidence": 0.0
                    }
                conv["intent_type"] = intent["intent_type"]
                return self.p_flow(self.cr.chat_data, conv)
            else:
                return self.p_flow(self.cr.chat_data, conv)
        else:
            return self.neg_res(self.cr.chat_data, conv)

    def intent_parser(self, conv):
        """
        Method to get intent of user text.
        :param conv: user data
        :return:
        """
        for intent in self.engine.determine_intent(conv):
            if intent.get('confidence') > 0:
                return json.dumps(intent, indent=4)
            else:
                pass

    def p_flow(self, corpus, ud):
        """
        Method to implement process for user.
        :param corpus: It provides data for chatbot
        :param ud: User related data.
        :return: response
        """
        response = dict(userId="123",
                        user_intent="",
                        response_text="",
                        card_type="",
                        recommendation=[],
                        entities=[],
                        user_stage=0)

        self.bot.log_debug("User Data: {}".format(ud))
        p_data = corpus[ud['intent_type']][ud['user_stage']]
        user_txt = self.clean_text(ud['user_text'])
        if user_txt in p_data.keys():
            response['response_text'] = random.choice(p_data[user_txt])
            response['card_type'] = ""

        elif user_txt in p_data['user_text']:
            print("p_data: >>" + str(p_data))
            response = self.update_res(response, p_data)

        elif user_txt == 'quit':
            q_data = corpus['EXIT'][0]
            response['user_intent'] = q_data['intent_type']
            response['user_stage'] = q_data['user_stage']
            response['response_text'] = random.choice(q_data['response'])
            response['card_type'] = q_data['card_type']

        else:
            self.build_train_cr(ud)
            if 'default_speech' in p_data.keys(
            ) and p_data['default_speech'] is not '':
                response['response_text'] = random.choice(
                    p_data['default_speech'])
            else:
                response[
                    'response_text'] = "Please select below option to move forward"
            response['card_type'] = "#IntentOptions"
            response['user_stage'] = ud['user_stage']
            response['user_intent'] = ud['intent_type']
            if len(p_data['recommendation']) == 0:
                response['recommendation'] = self.cr.all_skills
            else:
                response['recommendation'] = p_data['recommendation']
        print("response: " + str(response))
        return response

    def update_res(self, response, p_data):
        """
        :param response:
        :param p_data:
        :return:
        """
        response['response_text'] = random.choice(p_data['response'])
        response['user_intent'] = p_data['intent_type']
        response['card_type'] = p_data['card_type']
        response['user_stage'] = p_data['user_stage']
        response['entities'] = p_data['entities']

        return response

    def neg_res(self, cr_data, conv):
        """
        Generate response if sentiment is -ve
        :param cr_data:
        :param conv:
        :return:
        """
        neg_data = cr_data["neg_sent"][0]
        return dict(user_intent="",
                    response_text=random.choice(neg_data["response"]),
                    user_stage=0,
                    card_type='',
                    recommendation=[])

    def sent(self, conv):
        """
        Polarity is considered pos if > -0.2 (for tuning our scenario)
        :param conv:
        :return:
        """
        t = TextBlob(conv)
        vs = self.senti.polarity_scores(conv)
        return False if t.polarity < -0.3 else True

    def clean_text(self, statement):
        """
        Remove any consecutive whitespace characters from the statement text.
        """
        return re.sub(r'[^a-zA-Z0-9 ]', r'', statement).rstrip().lower()

    def build_train_cr(self, ud):
        intent = ud['intent_type']
        if intent is not '':
            self.train_data['intent'] = intent
            self.train_data['user_text'] = ud['user_text']
        else:
            self.train_data['intent'] = ""
            self.train_data['user_text'] = ud['user_text']

        with open('train_data.json', 'a') as f:
            json.dump(self.train_data, f, indent=True)


# bot = CrmnextChatBot()
# user_stage = 0
# intent = ''
# print(bot.clean_text("Hi I am #avinash ?  "))
# while True:
#
#     text = input("Enter text: ")
#     reconnect = input('re-connect: ')
#     print(bool(reconnect))
#     data = {'userId': '123', "intent_type":intent, "user_text": text, 'user_name': 'Avinash Gaur',
#             'contactNumber': '89892398128', 'cardCount': 2, 'user_stage': user_stage, 're_connect': bool(reconnect)}
#     print(data)
#     print(user_stage)
#     print(intent)
#     d = bot.run_bot(data)
#     intent = d['user_intent']
#     user_stage = d['user_stage']
#
#
#     print(d)
Exemplo n.º 30
0
class IntentService(object):
    def __init__(self, emitter):
        self.config = Configuration.get().get('context', {})
        self.engine = IntentDeterminationEngine()
        self.context_keywords = self.config.get('keywords', [])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)
        self.emitter = emitter
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)
        self.emitter.on('detach_skill', self.handle_detach_skill)
        # Context related handlers
        self.emitter.on('add_context', self.handle_add_context)
        self.emitter.on('remove_context', self.handle_remove_context)
        self.emitter.on('clear_context', self.handle_clear_context)
        # Converse method
        self.emitter.on('skill.converse.response',
                        self.handle_converse_response)
        self.active_skills = []  # [skill_id , timestamp]
        self.converse_timeout = 5  # minutes to prune active_skills

    def do_converse(self, utterances, skill_id, lang):
        self.emitter.emit(Message("skill.converse.request", {
            "skill_id": skill_id, "utterances": utterances, "lang": lang}))
        self.waiting = True
        self.result = False
        start_time = time.time()
        t = 0
        while self.waiting and t < 5:
            t = time.time() - start_time
            time.sleep(0.1)
        self.waiting = False
        return self.result

    def handle_converse_response(self, message):
        # id = message.data["skill_id"]
        # no need to crosscheck id because waiting before new request is made
        # no other skill will make this request is safe assumption
        result = message.data["result"]
        self.result = result
        self.waiting = False

    def remove_active_skill(self, skill_id):
        for skill in self.active_skills:
            if skill[0] == skill_id:
                self.active_skills.remove(skill)

    def add_active_skill(self, skill_id):
        # search the list for an existing entry that already contains it
        # and remove that reference
        self.remove_active_skill(skill_id)
        # add skill with timestamp to start of skill_list
        self.active_skills.insert(0, [skill_id, time.time()])

    def update_context(self, intent):
        """
            updates context with keyword from the intent.

            NOTE: This method currently won't handle one_of intent keywords
                  since it's not using quite the same format as other intent
                  keywords. This is under investigation in adapt, PR pending.

            Args:
                intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def handle_utterance(self, message):
        # Get language of the utterance
        lang = message.data.get('lang', None)
        if not lang:
            lang = "en-us"

        utterances = message.data.get('utterances', '')

        # check for conversation time-out
        self.active_skills = [skill for skill in self.active_skills
                              if time.time() - skill[
                                  1] <= self.converse_timeout * 60]

        # check if any skill wants to handle utterance
        for skill in self.active_skills:
            if self.do_converse(utterances, skill[0], lang):
                # update timestamp, or there will be a timeout where
                # intent stops conversing whether its being used or not
                self.add_active_skill(skill[0])
                return

        # no skill wants to handle utterance
        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(self.engine.determine_intent(
                    normalize(utterance, lang), 100,
                    include_tags=True,
                    context_manager=self.context_manager))
                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration:
                # don't show error in log
                continue
            except e:
                LOG.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            self.update_context(best_intent)
            reply = message.reply(
                best_intent.get('intent_type'), best_intent)
            self.emitter.emit(reply)
            # update active skills
            skill_id = int(best_intent['intent_type'].split(":")[0])
            self.add_active_skill(skill_id)

        else:
            self.emitter.emit(Message("intent_failure", {
                "utterance": utterances[0],
                "lang": lang
            }))

    def handle_register_vocab(self, message):
        start_concept = message.data.get('start')
        end_concept = message.data.get('end')
        regex_str = message.data.get('regex')
        alias_of = message.data.get('alias_of')
        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(
                start_concept, end_concept, alias_of=alias_of)

    def handle_register_intent(self, message):
        print "Registering: " + str(message.data)
        intent = open_intent_envelope(message)
        self.engine.register_intent_parser(intent)

    def handle_detach_intent(self, message):
        intent_name = message.data.get('intent_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name]
        self.engine.intent_parsers = new_parsers

    def handle_detach_skill(self, message):
        skill_id = message.data.get('skill_id')
        new_parsers = [
            p for p in self.engine.intent_parsers if
            not p.name.startswith(skill_id)]
        self.engine.intent_parsers = new_parsers

    def handle_add_context(self, message):
        """
            Handles adding context from the message bus.
            The data field must contain a context keyword and
            may contain a word if a specific word should be injected
            as a match for the provided context keyword.

        """
        entity = {'confidence': 1.0}
        context = message.data.get('context')
        word = message.data.get('word') or ''
        # if not a string type try creating a string from it
        if not isinstance(word, basestring):
            word = str(word)
        entity['data'] = [(word, context)]
        entity['match'] = word
        entity['key'] = word
        self.context_manager.inject_context(entity)

    def handle_remove_context(self, message):
        """
            Handles removing context from the message bus. The
            data field must contain the 'context' to remove.
        """
        context = message.data.get('context')
        if context:
            self.context_manager.remove_context(context)

    def handle_clear_context(self, message):
        """
            Clears all keywords from context.
        """
        self.context_manager.clear_context()
Exemplo n.º 31
0
class IntentEngineTests(unittest.TestCase):
    def setUp(self):
        self.engine = IntentDeterminationEngine()

    def testRegisterIntentParser(self):
        assert len(self.engine.intent_parsers) == 0
        try:
            self.engine.register_intent_parser("NOTAPARSER")
            assert "Did not fail to register invalid intent parser" and False
        except ValueError as e:
            pass
        parser = IntentBuilder("Intent").build()
        self.engine.register_intent_parser(parser)
        assert len(self.engine.intent_parsers) == 1

    def testRegisterRegexEntity(self):
        assert len(self.engine._regex_strings) == 0
        assert len(self.engine.regular_expressions_entities) == 0
        self.engine.register_regex_entity(".*")
        assert len(self.engine._regex_strings) == 1
        assert len(self.engine.regular_expressions_entities) == 1

    def testSelectBestIntent(self):
        parser1 = IntentBuilder("Parser1").require("Entity1").build()
        self.engine.register_intent_parser(parser1)
        self.engine.register_entity("tree", "Entity1")

        utterance = "go to the tree house"
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        parser2 = IntentBuilder("Parser2").require("Entity1").require(
            "Entity2").build()
        self.engine.register_intent_parser(parser2)
        self.engine.register_entity("house", "Entity2")
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser2'

    def testDropIntent(self):
        parser1 = IntentBuilder("Parser1").require("Entity1").build()
        self.engine.register_intent_parser(parser1)
        self.engine.register_entity("tree", "Entity1")

        parser2 = (IntentBuilder("Parser2").require("Entity1").require(
            "Entity2").build())
        self.engine.register_intent_parser(parser2)
        self.engine.register_entity("house", "Entity2")

        utterance = "go to the tree house"

        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser2'

        assert self.engine.drop_intent_parser('Parser2') is True
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

    def testDropEntity(self):
        parser1 = IntentBuilder("Parser1").require("Entity1").build()
        self.engine.register_intent_parser(parser1)
        self.engine.register_entity("laboratory", "Entity1")
        self.engine.register_entity("lab", "Entity1")

        utterance = "get out of my lab"
        utterance2 = "get out of my laboratory"
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        intent = next(self.engine.determine_intent(utterance2))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        # Remove Entity and re-register laboratory and make sure only that
        # matches.
        self.engine.drop_entity(entity_type='Entity1')
        self.engine.register_entity("laboratory", "Entity1")

        # Sentence containing lab should not produce any results
        with self.assertRaises(StopIteration):
            intent = next(self.engine.determine_intent(utterance))

        # But sentence with laboratory should
        intent = next(self.engine.determine_intent(utterance2))
        assert intent
        assert intent['intent_type'] == 'Parser1'

    def testCustomDropEntity(self):
        parser1 = (IntentBuilder("Parser1").one_of("Entity1",
                                                   "Entity2").build())
        self.engine.register_intent_parser(parser1)
        self.engine.register_entity("laboratory", "Entity1")
        self.engine.register_entity("lab", "Entity2")

        utterance = "get out of my lab"
        utterance2 = "get out of my laboratory"
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        intent = next(self.engine.determine_intent(utterance2))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        def matcher(data):
            return data[1].startswith('Entity')

        self.engine.drop_entity(match_func=matcher)
        self.engine.register_entity("laboratory", "Entity1")

        # Sentence containing lab should not produce any results
        with self.assertRaises(StopIteration):
            intent = next(self.engine.determine_intent(utterance))

        # But sentence with laboratory should
        intent = next(self.engine.determine_intent(utterance2))
        assert intent

    def testDropRegexEntity(self):
        self.engine.register_regex_entity(r"the dog (?P<Dog>.*)")
        self.engine.register_regex_entity(r"the cat (?P<Cat>.*)")
        assert len(self.engine._regex_strings) == 2
        assert len(self.engine.regular_expressions_entities) == 2
        self.engine.drop_regex_entity(entity_type='Cat')
        assert len(self.engine._regex_strings) == 1
        assert len(self.engine.regular_expressions_entities) == 1

    def testCustomDropRegexEntity(self):
        self.engine.register_regex_entity(r"the dog (?P<SkillADog>.*)")
        self.engine.register_regex_entity(r"the cat (?P<SkillACat>.*)")
        self.engine.register_regex_entity(r"the mangy dog (?P<SkillBDog>.*)")
        assert len(self.engine._regex_strings) == 3
        assert len(self.engine.regular_expressions_entities) == 3

        def matcher(regexp):
            """Matcher for all match groups defined for SkillB"""
            match_groups = regexp.groupindex.keys()
            return any([k.startswith('SkillB') for k in match_groups])

        self.engine.drop_regex_entity(match_func=matcher)
        assert len(self.engine._regex_strings) == 2
        assert len(self.engine.regular_expressions_entities) == 2
Exemplo n.º 32
0
class IntentService(object):
    def __init__(self, emitter):
        self.config = ConfigurationManager.get().get('context', {})
        self.engine = IntentDeterminationEngine()
        self.context_keywords = self.config.get('keywords', ['Location'])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)
        self.emitter = emitter
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)
        self.emitter.on('detach_skill', self.handle_detach_skill)
        # Context related handlers
        self.emitter.on('add_context', self.handle_add_context)
        self.emitter.on('remove_context', self.handle_remove_context)
        self.emitter.on('clear_context', self.handle_clear_context)
        # Converse method
        self.emitter.on('skill.converse.response',
                        self.handle_converse_response)
        self.active_skills = []  # [skill_id , timestamp]
        self.converse_timeout = 5  # minutes to prune active_skills

    def do_converse(self, utterances, skill_id, lang):
        self.emitter.emit(Message("skill.converse.request", {
            "skill_id": skill_id, "utterances": utterances, "lang": lang}))
        self.waiting = True
        self.result = False
        start_time = time.time()
        t = 0
        while self.waiting and t < 5:
            t = time.time() - start_time
            time.sleep(0.1)
        self.waiting = False
        return self.result

    def handle_converse_response(self, message):
        # id = message.data["skill_id"]
        # no need to crosscheck id because waiting before new request is made
        # no other skill will make this request is safe assumption
        result = message.data["result"]
        self.result = result
        self.waiting = False

    def remove_active_skill(self, skill_id):
        for skill in self.active_skills:
            if skill[0] == skill_id:
                self.active_skills.remove(skill)

    def add_active_skill(self, skill_id):
        # search the list for an existing entry that already contains it
        # and remove that reference
        self.remove_active_skill(skill_id)
        # add skill with timestamp to start of skill_list
        self.active_skills.insert(0, [skill_id, time.time()])

    def update_context(self, intent):
        """
            updates context with keyword from the intent.

            NOTE: This method currently won't handle one_of intent keywords
                  since it's not using quite the same format as other intent
                  keywords. This is under investigation in adapt, PR pending.

            Args:
                intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def handle_utterance(self, message):
        # Get language of the utterance
        lang = message.data.get('lang', None)
        if not lang:
            lang = "en-us"

        utterances = message.data.get('utterances', '')

        # check for conversation time-out
        self.active_skills = [skill for skill in self.active_skills
                              if time.time() - skill[
                                  1] <= self.converse_timeout * 60]

        # check if any skill wants to handle utterance
        for skill in self.active_skills:
            if self.do_converse(utterances, skill[0], lang):
                # update timestamp, or there will be a timeout where
                # intent stops conversing whether its being used or not
                self.add_active_skill(skill[0])
                return

        # no skill wants to handle utterance
        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(self.engine.determine_intent(
                    normalize(utterance, lang), 100,
                    include_tags=True,
                    context_manager=self.context_manager))
                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration, e:
                LOG.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            self.update_context(best_intent)
            reply = message.reply(
                best_intent.get('intent_type'), best_intent)
            self.emitter.emit(reply)
            # update active skills
            skill_id = int(best_intent['intent_type'].split(":")[0])
            self.add_active_skill(skill_id)

        else:
            self.emitter.emit(Message("intent_failure", {
                "utterance": utterances[0],
                "lang": lang
            }))
Exemplo n.º 33
0
from adapt.intent import IntentBuilder
from adapt.engine import IntentDeterminationEngine

engine = IntentDeterminationEngine()

schema = json.loads(sys.argv[1])

for entity in schema["entities"]:
	if entity["type"] == "string":
		for value in entity["values"]:
			engine.register_entity(value, entity["name"])
	elif entity["type"] == "regex":
		engine.register_regex_entity(entity["pattern"])

for intent in schema["intents"]:
	ib = IntentBuilder(intent["name"].encode("utf-8"))
	for requirement in intent["requirements"]:
		ib.require(requirement["entity"], requirement["attribute"])
	for optional in intent["optionals"]:
		ib.optionally(optional["entity"], optional["attribute"])
	engine.register_intent_parser(ib.build())

if __name__ == "__main__":
	while True:
		line = sys.stdin.readline()
		query = json.loads(line)
		intents = list(engine.determine_intent(query["input"]))
		response = {"intents": intents}
		print(json.dumps(response))
		sys.stdout.flush()
Exemplo n.º 34
0
class IntentService(object):
    def __init__(self, bus):
        self.config = Configuration.get().get('context', {})
        self.engine = IntentDeterminationEngine()

        # Dictionary for translating a skill id to a name
        self.skill_names = {}
        # Context related intializations
        self.context_keywords = self.config.get('keywords', [])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)
        self.bus = bus
        self.bus.on('register_vocab', self.handle_register_vocab)
        self.bus.on('register_intent', self.handle_register_intent)
        self.bus.on('recognizer_loop:utterance', self.handle_utterance)
        self.bus.on('detach_intent', self.handle_detach_intent)
        self.bus.on('detach_skill', self.handle_detach_skill)
        # Context related handlers
        self.bus.on('add_context', self.handle_add_context)
        self.bus.on('remove_context', self.handle_remove_context)
        self.bus.on('clear_context', self.handle_clear_context)
        # Converse method
        self.bus.on('skill.converse.response', self.handle_converse_response)
        self.bus.on('skill.converse.error', self.handle_converse_error)
        self.bus.on('mycroft.speech.recognition.unknown', self.reset_converse)
        self.bus.on('mycroft.skills.loaded', self.update_skill_name_dict)

        def add_active_skill_handler(message):
            self.add_active_skill(message.data['skill_id'])
        self.bus.on('active_skill_request', add_active_skill_handler)
        self.active_skills = []  # [skill_id , timestamp]
        self.converse_timeout = 5  # minutes to prune active_skills
        self.waiting_for_converse = False
        self.converse_result = False
        self.converse_skill_id = ""

    def update_skill_name_dict(self, message):
        """
            Messagebus handler, updates dictionary of if to skill name
            conversions.
        """
        self.skill_names[message.data['id']] = message.data['name']

    def get_skill_name(self, skill_id):
        """ Get skill name from skill ID.

        Args:
            skill_id: a skill id as encoded in Intent handlers.

        Returns:
            (str) Skill name or the skill id if the skill wasn't found
        """
        return self.skill_names.get(skill_id, skill_id)

    def reset_converse(self, message):
        """Let skills know there was a problem with speech recognition"""
        lang = message.data.get('lang', "en-us")
        for skill in self.active_skills:
            self.do_converse(None, skill[0], lang)

    def do_converse(self, utterances, skill_id, lang):
        self.waiting_for_converse = True
        self.converse_result = False
        self.converse_skill_id = skill_id
        self.bus.emit(Message("skill.converse.request", {
            "skill_id": skill_id, "utterances": utterances, "lang": lang}))
        start_time = time.time()
        t = 0
        while self.waiting_for_converse and t < 5:
            t = time.time() - start_time
            time.sleep(0.1)
        self.waiting_for_converse = False
        self.converse_skill_id = ""
        return self.converse_result

    def handle_converse_error(self, message):
        skill_id = message.data["skill_id"]
        if message.data["error"] == "skill id does not exist":
            self.remove_active_skill(skill_id)
        if skill_id == self.converse_skill_id:
            self.converse_result = False
            self.waiting_for_converse = False

    def handle_converse_response(self, message):
        skill_id = message.data["skill_id"]
        if skill_id == self.converse_skill_id:
            self.converse_result = message.data.get("result", False)
            self.waiting_for_converse = False

    def remove_active_skill(self, skill_id):
        for skill in self.active_skills:
            if skill[0] == skill_id:
                self.active_skills.remove(skill)

    def add_active_skill(self, skill_id):
        # search the list for an existing entry that already contains it
        # and remove that reference
        self.remove_active_skill(skill_id)
        # add skill with timestamp to start of skill_list
        self.active_skills.insert(0, [skill_id, time.time()])

    def update_context(self, intent):
        """ Updates context with keyword from the intent.

        NOTE: This method currently won't handle one_of intent keywords
              since it's not using quite the same format as other intent
              keywords. This is under investigation in adapt, PR pending.

        Args:
            intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def send_metrics(self, intent, context, stopwatch):
        """
        Send timing metrics to the backend.

        NOTE: This only applies to those with Opt In.
        """
        ident = context['ident'] if context else None
        if intent:
            # Recreate skill name from skill id
            parts = intent.get('intent_type', '').split(':')
            intent_type = self.get_skill_name(parts[0])
            if len(parts) > 1:
                intent_type = ':'.join([intent_type] + parts[1:])
            report_timing(ident, 'intent_service', stopwatch,
                          {'intent_type': intent_type})
        else:
            report_timing(ident, 'intent_service', stopwatch,
                          {'intent_type': 'intent_failure'})

    def handle_utterance(self, message):
        """ Main entrypoint for handling user utterances with Mycroft skills

        Monitor the messagebus for 'recognizer_loop:utterance', typically
        generated by a spoken interaction but potentially also from a CLI
        or other method of injecting a 'user utterance' into the system.

        Utterances then work through this sequence to be handled:
        1) Active skills attempt to handle using converse()
        2) Adapt intent handlers
        3) Padatious intent handlers
        4) Other fallbacks

        Args:
            message (Message): The messagebus data
        """
        try:
            # Get language of the utterance
            lang = message.data.get('lang', "en-us")
            utterances = message.data.get('utterances', '')

            stopwatch = Stopwatch()
            with stopwatch:
                # Give active skills an opportunity to handle the utterance
                converse = self._converse(utterances, lang)

                if not converse:
                    # No conversation, use intent system to handle utterance
                    intent = self._adapt_intent_match(utterances, lang)
                    padatious_intent = PadatiousService.instance.calc_intent(
                                        utterances[0])

            if converse:
                # Report that converse handled the intent and return
                ident = message.context['ident'] if message.context else None
                report_timing(ident, 'intent_service', stopwatch,
                              {'intent_type': 'converse'})
                return
            elif intent and not (padatious_intent and
                                 padatious_intent.conf >= 0.95):
                # Send the message to the Adapt intent's handler unless
                # Padatious is REALLY sure it was directed at it instead.
                reply = message.reply(intent.get('intent_type'), intent)
            else:
                # Allow fallback system to handle utterance
                # NOTE: Padatious intents are handled this way, too
                reply = message.reply('intent_failure',
                                      {'utterance': utterances[0],
                                       'lang': lang})
            self.bus.emit(reply)
            self.send_metrics(intent, message.context, stopwatch)
        except Exception as e:
            LOG.exception(e)

    def _converse(self, utterances, lang):
        """ Give active skills a chance at the utterance

        Args:
            utterances (list):  list of utterances
            lang (string):      4 letter ISO language code

        Returns:
            bool: True if converse handled it, False if  no skill processes it
        """

        # check for conversation time-out
        self.active_skills = [skill for skill in self.active_skills
                              if time.time() - skill[
                                  1] <= self.converse_timeout * 60]

        # check if any skill wants to handle utterance
        for skill in self.active_skills:
            if self.do_converse(utterances, skill[0], lang):
                # update timestamp, or there will be a timeout where
                # intent stops conversing whether its being used or not
                self.add_active_skill(skill[0])
                return True
        return False

    def _adapt_intent_match(self, utterances, lang):
        """ Run the Adapt engine to search for an matching intent

        Args:
            utterances (list):  list of utterances
            lang (string):      4 letter ISO language code

        Returns:
            Intent structure, or None if no match was found.
        """
        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(self.engine.determine_intent(
                    normalize(utterance, lang), 100,
                    include_tags=True,
                    context_manager=self.context_manager))
                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration:
                # don't show error in log
                continue
            except Exception as e:
                LOG.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            self.update_context(best_intent)
            # update active skills
            skill_id = best_intent['intent_type'].split(":")[0]
            self.add_active_skill(skill_id)
            # adapt doesn't handle context injection for one_of keywords
            # correctly. Workaround this issue if possible.
            try:
                best_intent = workaround_one_of_context(best_intent)
            except LookupError:
                LOG.error('Error during workaround_one_of_context')
            return best_intent

    def handle_register_vocab(self, message):
        start_concept = message.data.get('start')
        end_concept = message.data.get('end')
        regex_str = message.data.get('regex')
        alias_of = message.data.get('alias_of')
        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(
                start_concept, end_concept, alias_of=alias_of)

    def handle_register_intent(self, message):
        intent = open_intent_envelope(message)
        self.engine.register_intent_parser(intent)

    def handle_detach_intent(self, message):
        intent_name = message.data.get('intent_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name]
        self.engine.intent_parsers = new_parsers

    def handle_detach_skill(self, message):
        skill_id = message.data.get('skill_id')
        new_parsers = [
            p for p in self.engine.intent_parsers if
            not p.name.startswith(skill_id)]
        self.engine.intent_parsers = new_parsers

    def handle_add_context(self, message):
        """ Add context

        Args:
            message: data contains the 'context' item to add
                     optionally can include 'word' to be injected as
                     an alias for the context item.
        """
        entity = {'confidence': 1.0}
        context = message.data.get('context')
        word = message.data.get('word') or ''
        origin = message.data.get('origin') or ''
        # if not a string type try creating a string from it
        if not isinstance(word, str):
            word = str(word)
        entity['data'] = [(word, context)]
        entity['match'] = word
        entity['key'] = word
        entity['origin'] = origin
        self.context_manager.inject_context(entity)

    def handle_remove_context(self, message):
        """ Remove specific context

        Args:
            message: data contains the 'context' item to remove
        """
        context = message.data.get('context')
        if context:
            self.context_manager.remove_context(context)

    def handle_clear_context(self, message):
        """ Clears all keywords from context """
        self.context_manager.clear_context()
Exemplo n.º 35
0
class IntentService(object):
    def __init__(self, emitter):
        self.engine = IntentDeterminationEngine()
        self.emitter = emitter
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)

        self.emitter.on('onyx_recognizer:utterance', self.handle_utterance)

        self.emitter.on('detach_intent', self.handle_detach_intent)
        self.emitter.on('detach_skill', self.handle_detach_skill)

    def handle_utterance(self, message):

        lang = message.data.get('lang', None)
        if not lang:
            lang = "en-US"

        user = message.data.get('user', None)

        url = message.data.get('url', None)

        utterances = message.data.get('utterances', '')

        best_intent = None

        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy"
                best_intent = next(
                    self.engine.determine_intent(normalize(utterance, lang),
                                                 100))

                best_intent['utterance'] = utterance
            except StopIteration as e:
                logger.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            best_intent['lang'] = lang
            best_intent['user'] = user

            reply = message.reply(best_intent.get('intent_type'), best_intent)
            self.emitter.emit(reply)
        elif len(utterances) == 1:
            self.emitter.emit(
                Message("intent_failure", {
                    "utterance": utterances[0],
                    "lang": lang
                }))
        else:
            self.emitter.emit(
                Message("multi_utterance_intent_failure", {
                    "utterances": utterances,
                    "lang": lang
                }))

    def handle_register_vocab(self, message):

        start_concept = message.data.get('start')
        end_concept = message.data.get('end')
        regex_str = message.data.get('regex')
        alias_of = message.data.get('alias_of')

        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(start_concept,
                                        end_concept,
                                        alias_of=alias_of)

    def handle_register_intent(self, message):
        intent = open_intent_envelope(message)
        self.engine.register_intent_parser(intent)

    def handle_detach_intent(self, message):
        intent_name = message.data.get('intent_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name
        ]
        self.engine.intent_parsers = new_parsers

    def handle_detach_skill(self, message):
        skill_name = message.data.get('skill_name')
        new_parsers = [
            p for p in self.engine.intent_parsers
            if not p.name.startswith(skill_name)
        ]
        self.engine.intent_parsers = new_parsers
Exemplo n.º 36
0
class IntentEngineTests(unittest.TestCase):
    def setUp(self):
        self.engine = IntentDeterminationEngine()

    def testRegisterIntentParser(self):
        assert len(self.engine.intent_parsers) == 0
        try:
            self.engine.register_intent_parser("NOTAPARSER")
            assert "Did not fail to register invalid intent parser" and False
        except ValueError as e:
            pass
        parser = IntentBuilder("Intent").build()
        self.engine.register_intent_parser(parser)
        assert len(self.engine.intent_parsers) == 1

    def testRegisterRegexEntity(self):
        assert len(self.engine._regex_strings) == 0
        assert len(self.engine.regular_expressions_entities) == 0
        self.engine.register_regex_entity(".*")
        assert len(self.engine._regex_strings) == 1
        assert len(self.engine.regular_expressions_entities) == 1

    def testSelectBestIntent(self):
        parser1 = IntentBuilder("Parser1").require("Entity1").build()
        self.engine.register_intent_parser(parser1)
        self.engine.register_entity("tree", "Entity1")

        utterance = "go to the tree house"
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        parser2 = IntentBuilder("Parser2").require("Entity1").require(
            "Entity2").build()
        self.engine.register_intent_parser(parser2)
        self.engine.register_entity("house", "Entity2")
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser2'

    def testDropIntent(self):
        parser1 = IntentBuilder("Parser1").require("Entity1").build()
        self.engine.register_intent_parser(parser1)
        self.engine.register_entity("tree", "Entity1")

        parser2 = (IntentBuilder("Parser2").require("Entity1").require(
            "Entity2").build())
        self.engine.register_intent_parser(parser2)
        self.engine.register_entity("house", "Entity2")

        utterance = "go to the tree house"

        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser2'

        assert self.engine.drop_intent_parser('Parser2') is True
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

    def testDropEntity(self):
        parser1 = IntentBuilder("Parser1").require("Entity1").build()
        self.engine.register_intent_parser(parser1)
        self.engine.register_entity("laboratory", "Entity1")
        self.engine.register_entity("lab", "Entity1")

        utterance = "get out of my lab"
        utterance2 = "get out of my laboratory"
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        intent = next(self.engine.determine_intent(utterance2))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        # Remove Entity and re-register laboratory and make sure only that
        # matches.
        self.engine.drop_entity(entity_type='Entity1')
        self.engine.register_entity("laboratory", "Entity1")

        # Sentence containing lab should not produce any results
        with self.assertRaises(StopIteration):
            intent = next(self.engine.determine_intent(utterance))

        # But sentence with laboratory should
        intent = next(self.engine.determine_intent(utterance2))
        assert intent
        assert intent['intent_type'] == 'Parser1'

    def testCustomDropEntity(self):
        parser1 = (IntentBuilder("Parser1").one_of("Entity1",
                                                   "Entity2").build())
        self.engine.register_intent_parser(parser1)
        self.engine.register_entity("laboratory", "Entity1")
        self.engine.register_entity("lab", "Entity2")

        utterance = "get out of my lab"
        utterance2 = "get out of my laboratory"
        intent = next(self.engine.determine_intent(utterance))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        intent = next(self.engine.determine_intent(utterance2))
        assert intent
        assert intent['intent_type'] == 'Parser1'

        def matcher(data):
            return data[1].startswith('Entity')

        self.engine.drop_entity(match_func=matcher)
        self.engine.register_entity("laboratory", "Entity1")

        # Sentence containing lab should not produce any results
        with self.assertRaises(StopIteration):
            intent = next(self.engine.determine_intent(utterance))

        # But sentence with laboratory should
        intent = next(self.engine.determine_intent(utterance2))
        assert intent

    def testDropRegexEntity(self):
        self.engine.register_regex_entity(r"the dog (?P<Dog>.*)")
        self.engine.register_regex_entity(r"the cat (?P<Cat>.*)")
        assert len(self.engine._regex_strings) == 2
        assert len(self.engine.regular_expressions_entities) == 2
        self.engine.drop_regex_entity(entity_type='Cat')
        assert len(self.engine._regex_strings) == 1
        assert len(self.engine.regular_expressions_entities) == 1

    def testCustomDropRegexEntity(self):
        self.engine.register_regex_entity(r"the dog (?P<SkillADog>.*)")
        self.engine.register_regex_entity(r"the cat (?P<SkillACat>.*)")
        self.engine.register_regex_entity(r"the mangy dog (?P<SkillBDog>.*)")
        assert len(self.engine._regex_strings) == 3
        assert len(self.engine.regular_expressions_entities) == 3

        def matcher(regexp):
            """Matcher for all match groups defined for SkillB"""
            match_groups = regexp.groupindex.keys()
            return any([k.startswith('SkillB') for k in match_groups])

        self.engine.drop_regex_entity(match_func=matcher)
        assert len(self.engine._regex_strings) == 2
        assert len(self.engine.regular_expressions_entities) == 2

    def testAddingOfRemovedRegexp(self):
        self.engine.register_regex_entity(r"the cool (?P<thing>.*)")

        def matcher(regexp):
            """Matcher for all match groups defined for SkillB"""
            match_groups = regexp.groupindex.keys()
            return any([k.startswith('thing') for k in match_groups])

        self.engine.drop_regex_entity(match_func=matcher)
        assert len(self.engine.regular_expressions_entities) == 0
        self.engine.register_regex_entity(r"the cool (?P<thing>.*)")
        assert len(self.engine.regular_expressions_entities) == 1

    def testUsingOfRemovedRegexp(self):
        self.engine.register_regex_entity(r"the cool (?P<thing>.*)")
        parser = IntentBuilder("Intent").require("thing").build()
        self.engine.register_intent_parser(parser)

        def matcher(regexp):
            """Matcher for all match groups defined for SkillB"""
            match_groups = regexp.groupindex.keys()
            return any([k.startswith('thing') for k in match_groups])

        self.engine.drop_regex_entity(match_func=matcher)
        assert len(self.engine.regular_expressions_entities) == 0

        utterance = "the cool cat"
        intents = [match for match in self.engine.determine_intent(utterance)]
        assert len(intents) == 0

    def testEmptyTags(self):
        # Validates https://github.com/MycroftAI/adapt/issues/114
        engine = IntentDeterminationEngine()
        engine.register_entity("Kevin",
                               "who")  # same problem if several entities
        builder = IntentBuilder("Buddies")
        builder.optionally("who")  # same problem if several entity types
        engine.register_intent_parser(builder.build())

        intents = [i for i in engine.determine_intent("Julien is a friend")]
        assert len(intents) == 0

    def testResultsAreSortedByConfidence(self):
        self.engine.register_entity('what is', 'Query', None)
        self.engine.register_entity('weather', 'Weather', None)
        self.engine.register_regex_entity('(at|in) (?P<Location>.+)')
        self.engine.register_regex_entity('(?P<Entity>.*)')

        i = IntentBuilder("CurrentWeatherIntent").require(
            "Weather").optionally("Location").build()
        self.engine.register_intent_parser(i)
        utterance = "what is the weather like in stockholm"
        intents = [
            i for i in self.engine.determine_intent(utterance, num_results=100)
        ]
        confidences = [intent.get('confidence', 0.0) for intent in intents]
        assert len(confidences) > 1
        assert all(confidences[i] >= confidences[i + 1]
                   for i in range(len(confidences) - 1))
Exemplo n.º 37
0
class IntentSkill(BoomerSkill):
    def __init__(self):
        BoomerSkill.__init__(self, name="IntentSkill")
        self.engine = IntentDeterminationEngine()

    def initialize(self):
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)

    def handle_utterance(self, message):
        timer = Stopwatch()
        timer.start()

        metrics = MetricsAggregator()
        utterances = message.data.get('utterances', '')

        best_intent = None

        for utterance in utterances:
            metrics.increment("utterances.count")
            for intent in self.engine.determine_intent(
                    utterance, num_results=100):
                metrics.increment("intents.count")
                intent['utterance'] = utterance
                best_confidence = best_intent.get('confidence') \
                    if best_intent else 0.0
                cur_confidence = intent.get('confidence', 0.0)
                if best_confidence < cur_confidence:
                    best_intent = intent

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            reply = message.reply(
                best_intent.get('intent_type'), data=best_intent)
            self.emitter.emit(reply)
        elif len(utterances) == 1:
            self.emitter.emit(
                Message("intent_failure",
                        data={"utterance": utterances[0]}))
        else:
            self.emitter.emit(
                Message("multi_utterance_intent_failure",
                        data={"utterances": utterances}))
        metrics.timer("parse.time", timer.stop())
        metrics.flush()

    def handle_register_vocab(self, message):
        start_concept = message.data.get('start')
        end_concept = message.data.get('end')
        regex_str = message.data.get('regex')
        alias_of = message.data.get('alias_of')
        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(
                start_concept, end_concept, alias_of=alias_of)

    def handle_register_intent(self, message):
        intent = open_intent_envelope(message)
        self.engine.register_intent_parser(intent)

    def handle_detach_intent(self, message):
        intent_name = message.data.get('intent_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name]
        self.engine.intent_parsers = new_parsers

    def stop(self):
        pass
Exemplo n.º 38
0
class IntentService(object):
    def __init__(self, emitter):
        self.config = ConfigurationManager.get().get('context', {})
        self.engine = IntentDeterminationEngine()
        self.context_keywords = self.config.get('keywords', ['Location'])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)
        self.emitter = emitter
        self.emitter.on('register_vocab', self.handle_register_vocab)
        self.emitter.on('register_intent', self.handle_register_intent)
        self.emitter.on('recognizer_loop:utterance', self.handle_utterance)
        self.emitter.on('detach_intent', self.handle_detach_intent)
        self.emitter.on('detach_skill', self.handle_detach_skill)
        # Context related handlers
        self.emitter.on('add_context', self.handle_add_context)
        self.emitter.on('remove_context', self.handle_remove_context)
        self.emitter.on('clear_context', self.handle_clear_context)
        # Converse method
        self.emitter.on('skill.converse.response',
                        self.handle_converse_response)
        self.active_skills = []  # [skill_id , timestamp]
        self.converse_timeout = 5  # minutes to prune active_skills

    def do_converse(self, utterances, skill_id, lang):
        self.emitter.emit(
            Message("skill.converse.request", {
                "skill_id": skill_id,
                "utterances": utterances,
                "lang": lang
            }))
        self.waiting = True
        self.result = False
        start_time = time.time()
        t = 0
        while self.waiting and t < 5:
            t = time.time() - start_time
            time.sleep(0.1)
        self.waiting = False
        return self.result

    def handle_converse_response(self, message):
        # id = message.data["skill_id"]
        # no need to crosscheck id because waiting before new request is made
        # no other skill will make this request is safe assumption
        result = message.data["result"]
        self.result = result
        self.waiting = False

    def remove_active_skill(self, skill_id):
        for skill in self.active_skills:
            if skill[0] == skill_id:
                self.active_skills.remove(skill)

    def add_active_skill(self, skill_id):
        # search the list for an existing entry that already contains it
        # and remove that reference
        self.remove_active_skill(skill_id)
        # add skill with timestamp to start of skill_list
        self.active_skills.insert(0, [skill_id, time.time()])

    def update_context(self, intent):
        """
            updates context with keyword from the intent.

            NOTE: This method currently won't handle one_of intent keywords
                  since it's not using quite the same format as other intent
                  keywords. This is under investigation in adapt, PR pending.

            Args:
                intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def handle_utterance(self, message):
        # Get language of the utterance
        lang = message.data.get('lang', None)
        if not lang:
            lang = "en-us"

        utterances = message.data.get('utterances', '')

        # check for conversation time-out
        self.active_skills = [
            skill for skill in self.active_skills
            if time.time() - skill[1] <= self.converse_timeout * 60
        ]

        # check if any skill wants to handle utterance
        for skill in self.active_skills:
            if self.do_converse(utterances, skill[0], lang):
                # update timestamp, or there will be a timeout where
                # intent stops conversing whether its being used or not
                self.add_active_skill(skill[0])
                return

        # no skill wants to handle utterance
        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(
                    self.engine.determine_intent(
                        normalize(utterance, lang),
                        100,
                        include_tags=True,
                        context_manager=self.context_manager))
                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration, e:
                LOG.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            self.update_context(best_intent)
            reply = message.reply(best_intent.get('intent_type'), best_intent)
            self.emitter.emit(reply)
            # update active skills
            skill_id = int(best_intent['intent_type'].split(":")[0])
            self.add_active_skill(skill_id)

        else:
            self.emitter.emit(
                Message("intent_failure", {
                    "utterance": utterances[0],
                    "lang": lang
                }))
Exemplo n.º 39
0
    engine.register_regex_entity(regex)
for regex in location_regex_keywords:
    engine.register_regex_entity(regex)
for regex in with_regex_keywords:
    engine.register_regex_entity(regex)
# construt an intent parser
add_event_intent = IntentBuilder('EventIntent') \
    .require('AddKeyword') \
    .require('EventKeyword') \
    .require('Personkeyword') \
    .optionally('LocationKeyword') \
    .optionally('Date') \
    .build()
engine.register_intent_parser(add_event_intent)
x='Add event with Hana Bouzid in Midoune Room starts 10 am'
for intent in engine.determine_intent(x):
    clist=[]
    alist = x.split("with ")
    print(alist)
    blist=alist[1].split(" in")
    lliste=blist[1].split("starts")
    print(blist)
    print(lliste)
    if("and")in blist[0]:
        clist = blist[0].split("and")
        print(clist)  # liste des attendees cités dans x
        print(type(clist))
    else :
        clist.append(blist[0])
        print(clist) #liste des attendees cités dans x
        print(type(clist))
Exemplo n.º 40
0
class VoiceCommands:
    ACTIONS = 'Action'
    NAME = 'Name'
    ACTUATOR_TYPE = 'ActuatorType'

    @typechecked()
    def __init__(self, job_controll: AsyncActuatorCommands,
                 logging: RootLogger):
        self.__job_controll = job_controll
        self.__logging = logging

    def configure(self):
        self.__engine = IntentDeterminationEngine()
        actions = ['on', 'off']
        self.__register_entity(actions, self.ACTIONS)
        locations = ['living', 'kitchen', 'hollway', 'wemo']
        self.__register_entity(locations, self.NAME)
        actuator_types = ['light', 'switch', 'courtains', 'door']
        self.__register_entity(actuator_types, self.ACTUATOR_TYPE)

        actuator_intent = IntentBuilder("ActuatorIntent") \
            .require(self.ACTIONS) \
            .require(self.ACTUATOR_TYPE) \
            .require(self.NAME) \
            .build()
        self.__engine.register_intent_parser(actuator_intent)
        self.__commands_map = [
            {
                'entities': {
                    'name': 'living',
                    'type': 'light'
                },
                'actuator': 'livingLight'
            },
            {
                'entities': {
                    'name': 'living',
                    'type': 'courtains'
                },
                'actuator': 'livingCourtains'
            },
            {
                'entities': {
                    'name': 'hollway',
                    'type': 'light'
                },
                'actuator': 'holwayLight'
            },
            {
                'entities': {
                    'name': 'wemo',
                    'type': 'switch'
                },
                'actuator': 'wemoSwitch1'
            },
        ]

        return self

    @typechecked()
    def execute(self, command: str) -> None:
        command = self.__normalize_command(command)
        for intent in self.__engine.determine_intent(command):
            if intent and intent.get('confidence') > 0:
                self.__logging.info(intent)
                command = self.__get_matching_command(intent)
                self.__run_command(command, intent)

    def __register_entity(self, wordlist, name):
        for action in wordlist:
            self.__engine.register_entity(action, name)

    def __get_matching_command(self, intent):
        for command in self.__commands_map:
            if command['entities']['name'] == intent[self.NAME] and \
                command['entities']['type'] == intent[self.ACTUATOR_TYPE]:
                return command

    def __run_command(self, command, intent):
        if None == command:
            return
        actuator_state = (False, True)[intent[self.ACTIONS] == 'on']
        self.__logging.info('Changin actuator {0} value: {1}'.format(
            command['actuator'], actuator_state))
        self.__job_controll.change_actuator(command['actuator'],
                                            actuator_state)

    def __normalize_command(self, command):
        replaces = [('life', 'light'), ('leaving', 'living'),
                    ('hallway', 'hollway'), ('quarters', 'courtains')]
        for replace in replaces:
            command = re.sub(replace[0], replace[1], command)

        return command
Exemplo n.º 41
0
class Brain(AppDaemon):
    def initialize(self):
        self.morph = pymorphy2.MorphAnalyzer()
        self.engine = IntentDeterminationEngine()
        self.context_managers = {}
        self.last_request = {}

    def query(self, query, connector):
        nquery = self.normalize(query)
        if not self.context_managers.get(connector):
            self.context_managers[connector] = ContextManager()
        if not self.last_request.get(connector):
            self.last_request[connector] = datetime.datetime.now()
        last_rq_delta = datetime.datetime.now() - self.last_request[connector]
        if last_rq_delta.seconds > 300:
            # resetting context, if last conversation was more than 15 minutes
            # ago
            self.context_managers[connector].frame_stack = []
        message = "Прости, что то пошло не так"
        for intent in self.engine.determine_intent(
                nquery,
                include_tags=True,
                context_manager=self.context_managers[connector]):
            # after enabling context management, it can become more important
            if intent and intent.get('confidence') > 0:
                if self.get_app(intent["intent_type"]).context_sensitive:
                    for tag in intent["__tags__"]:
                        context_entity = tag.get('entities')[0]
                        if self.get_app(intent["intent_type"]).context_blacklist\
                                and context_entity['data'][0][1]\
                                in self.get_app(intent["intent_type"]).context_blacklist:
                            continue
                        self.context_managers[connector].inject_context(
                            context_entity)
                message = self.get_app(intent["intent_type"]).handle(intent)
        self.last_request[connector] = datetime.datetime.now()
        # Some code for self-analyze
        self.get_app("analysis").add_entry(
            connector, intent["intent_type"], query,
            len(self.context_managers[connector].frame_stack))
        self.get_app(connector).answer(message)

    def normalize(self, text):
        """Remove punctuation, set all words to normal form"""
        nwords = []
        text = re.sub('\?|\!|\.|\,', '', text).lower()
        for word in text.split(' '):
            nword = self.morph.parse(word)[0].normal_form
            nwords.append(nword)
        nwords = " ".join(nwords)
        nwords = text_to_number(nwords)
        return nwords

    def read_dialog_files(self, dialog_file):
        """
        Reads file with phrases and returns list of them
        """
        dialog = []
        with open(dialog_file, 'r') as d:
            for line in d:
                dialog.append(line[:-1])
        return dialog

    def my_name(self):
        names = ["Jumper"]
        return random.choice(names)

    def greeting(self):
        phrases = [
            "Голосовой интерфейс активирован", "%s, я тут!", "Привет, %s!"
        ]
        phrase = random.choice(phrases)
        try:
            return phrase % self.my_name()
        except TypeError:
            return phrase

    def dont_understand(self):
        phrases = [
            "Прости, %s, я не поняла", "Повтори пожалуйста", "Прости, что?"
        ]
        phrase = random.choice(phrases)
        try:
            return phrase % self.my_name()
        except TypeError:
            return phrase
Exemplo n.º 42
0
class IntentService(object):
    def __init__(self, bus):
        self.config = Configuration.get().get('context', {})
        self.engine = IntentDeterminationEngine()

        # Dictionary for translating a skill id to a name
        self.skill_names = {}
        # Context related intializations
        self.context_keywords = self.config.get('keywords', [])
        self.context_max_frames = self.config.get('max_frames', 3)
        self.context_timeout = self.config.get('timeout', 2)
        self.context_greedy = self.config.get('greedy', False)
        self.context_manager = ContextManager(self.context_timeout)
        self.bus = bus
        self.bus.on('register_vocab', self.handle_register_vocab)
        self.bus.on('register_intent', self.handle_register_intent)
        self.bus.on('recognizer_loop:utterance', self.handle_utterance)
        self.bus.on('detach_intent', self.handle_detach_intent)
        self.bus.on('detach_skill', self.handle_detach_skill)
        # Context related handlers
        self.bus.on('add_context', self.handle_add_context)
        self.bus.on('remove_context', self.handle_remove_context)
        self.bus.on('clear_context', self.handle_clear_context)
        # Converse method
        self.bus.on('skill.converse.response', self.handle_converse_response)
        self.bus.on('skill.converse.error', self.handle_converse_error)
        self.bus.on('mycroft.speech.recognition.unknown', self.reset_converse)
        self.bus.on('mycroft.skills.loaded', self.update_skill_name_dict)

        def add_active_skill_handler(message):
            self.add_active_skill(message.data['skill_id'])

        self.bus.on('active_skill_request', add_active_skill_handler)
        self.active_skills = []  # [skill_id , timestamp]
        self.converse_timeout = 5  # minutes to prune active_skills
        self.waiting_for_converse = False
        self.converse_result = False
        self.converse_skill_id = ""

    def update_skill_name_dict(self, message):
        """
            Messagebus handler, updates dictionary of if to skill name
            conversions.
        """
        self.skill_names[message.data['id']] = message.data['name']

    def get_skill_name(self, skill_id):
        """ Get skill name from skill ID.

        Args:
            skill_id: a skill id as encoded in Intent handlers.

        Returns:
            (str) Skill name or the skill id if the skill wasn't found
        """
        return self.skill_names.get(skill_id, skill_id)

    def reset_converse(self, message):
        """Let skills know there was a problem with speech recognition"""
        lang = message.data.get('lang', "en-us")
        for skill in self.active_skills:
            self.do_converse(None, skill[0], lang)

    def do_converse(self, utterances, skill_id, lang):
        self.waiting_for_converse = True
        self.converse_result = False
        self.converse_skill_id = skill_id
        self.bus.emit(
            Message("skill.converse.request", {
                "skill_id": skill_id,
                "utterances": utterances,
                "lang": lang
            }))
        start_time = time.time()
        t = 0
        while self.waiting_for_converse and t < 5:
            t = time.time() - start_time
            time.sleep(0.1)
        self.waiting_for_converse = False
        self.converse_skill_id = ""
        return self.converse_result

    def handle_converse_error(self, message):
        skill_id = message.data["skill_id"]
        if message.data["error"] == "skill id does not exist":
            self.remove_active_skill(skill_id)
        if skill_id == self.converse_skill_id:
            self.converse_result = False
            self.waiting_for_converse = False

    def handle_converse_response(self, message):
        skill_id = message.data["skill_id"]
        if skill_id == self.converse_skill_id:
            self.converse_result = message.data.get("result", False)
            self.waiting_for_converse = False

    def remove_active_skill(self, skill_id):
        for skill in self.active_skills:
            if skill[0] == skill_id:
                self.active_skills.remove(skill)

    def add_active_skill(self, skill_id):
        # search the list for an existing entry that already contains it
        # and remove that reference
        self.remove_active_skill(skill_id)
        # add skill with timestamp to start of skill_list
        self.active_skills.insert(0, [skill_id, time.time()])

    def update_context(self, intent):
        """ Updates context with keyword from the intent.

        NOTE: This method currently won't handle one_of intent keywords
              since it's not using quite the same format as other intent
              keywords. This is under investigation in adapt, PR pending.

        Args:
            intent: Intent to scan for keywords
        """
        for tag in intent['__tags__']:
            if 'entities' not in tag:
                continue
            context_entity = tag['entities'][0]
            if self.context_greedy:
                self.context_manager.inject_context(context_entity)
            elif context_entity['data'][0][1] in self.context_keywords:
                self.context_manager.inject_context(context_entity)

    def send_metrics(self, intent, context, stopwatch):
        """
        Send timing metrics to the backend.

        NOTE: This only applies to those with Opt In.
        """
        ident = context['ident'] if context else None
        if intent:
            # Recreate skill name from skill id
            parts = intent.get('intent_type', '').split(':')
            intent_type = self.get_skill_name(parts[0])
            if len(parts) > 1:
                intent_type = ':'.join([intent_type] + parts[1:])
            report_timing(ident, 'intent_service', stopwatch,
                          {'intent_type': intent_type})
        else:
            report_timing(ident, 'intent_service', stopwatch,
                          {'intent_type': 'intent_failure'})

    def handle_utterance(self, message):
        """ Main entrypoint for handling user utterances with Mycroft skills

        Monitor the messagebus for 'recognizer_loop:utterance', typically
        generated by a spoken interaction but potentially also from a CLI
        or other method of injecting a 'user utterance' into the system.

        Utterances then work through this sequence to be handled:
        1) Active skills attempt to handle using converse()
        2) Adapt intent handlers
        3) Padatious intent handlers
        4) Other fallbacks

        Args:
            message (Message): The messagebus data
        """
        try:
            # Get language of the utterance
            lang = message.data.get('lang', "en-us")
            utterances = message.data.get('utterances', '')

            stopwatch = Stopwatch()
            with stopwatch:
                # Give active skills an opportunity to handle the utterance
                converse = self._converse(utterances, lang)

                if not converse:
                    # No conversation, use intent system to handle utterance
                    intent = self._adapt_intent_match(utterances, lang)
                    padatious_intent = PadatiousService.instance.calc_intent(
                        utterances[0])

            if converse:
                # Report that converse handled the intent and return
                ident = message.context['ident'] if message.context else None
                report_timing(ident, 'intent_service', stopwatch,
                              {'intent_type': 'converse'})
                return
            elif intent and not (padatious_intent
                                 and padatious_intent.conf >= 0.95):
                # Send the message to the Adapt intent's handler unless
                # Padatious is REALLY sure it was directed at it instead.
                reply = message.reply(intent.get('intent_type'), intent)
            else:
                # Allow fallback system to handle utterance
                # NOTE: Padatious intents are handled this way, too
                reply = message.reply('intent_failure', {
                    'utterance': utterances[0],
                    'lang': lang
                })
            self.bus.emit(reply)
            self.send_metrics(intent, message.context, stopwatch)
        except Exception as e:
            LOG.exception(e)

    def _converse(self, utterances, lang):
        """ Give active skills a chance at the utterance

        Args:
            utterances (list):  list of utterances
            lang (string):      4 letter ISO language code

        Returns:
            bool: True if converse handled it, False if  no skill processes it
        """

        # check for conversation time-out
        self.active_skills = [
            skill for skill in self.active_skills
            if time.time() - skill[1] <= self.converse_timeout * 60
        ]

        # check if any skill wants to handle utterance
        for skill in self.active_skills:
            if self.do_converse(utterances, skill[0], lang):
                # update timestamp, or there will be a timeout where
                # intent stops conversing whether its being used or not
                self.add_active_skill(skill[0])
                return True
        return False

    def _adapt_intent_match(self, utterances, lang):
        """ Run the Adapt engine to search for an matching intent

        Args:
            utterances (list):  list of utterances
            lang (string):      4 letter ISO language code

        Returns:
            Intent structure, or None if no match was found.
        """
        best_intent = None
        for utterance in utterances:
            try:
                # normalize() changes "it's a boy" to "it is boy", etc.
                best_intent = next(
                    self.engine.determine_intent(
                        normalize(utterance, lang),
                        100,
                        include_tags=True,
                        context_manager=self.context_manager))
                # TODO - Should Adapt handle this?
                best_intent['utterance'] = utterance
            except StopIteration:
                # don't show error in log
                continue
            except Exception as e:
                LOG.exception(e)
                continue

        if best_intent and best_intent.get('confidence', 0.0) > 0.0:
            self.update_context(best_intent)
            # update active skills
            skill_id = best_intent['intent_type'].split(":")[0]
            self.add_active_skill(skill_id)
            # adapt doesn't handle context injection for one_of keywords
            # correctly. Workaround this issue if possible.
            try:
                best_intent = workaround_one_of_context(best_intent)
            except LookupError:
                LOG.error('Error during workaround_one_of_context')
            return best_intent

    def handle_register_vocab(self, message):
        start_concept = message.data.get('start')
        end_concept = message.data.get('end')
        regex_str = message.data.get('regex')
        alias_of = message.data.get('alias_of')
        if regex_str:
            self.engine.register_regex_entity(regex_str)
        else:
            self.engine.register_entity(start_concept,
                                        end_concept,
                                        alias_of=alias_of)

    def handle_register_intent(self, message):
        intent = open_intent_envelope(message)
        self.engine.register_intent_parser(intent)

    def handle_detach_intent(self, message):
        intent_name = message.data.get('intent_name')
        new_parsers = [
            p for p in self.engine.intent_parsers if p.name != intent_name
        ]
        self.engine.intent_parsers = new_parsers

    def handle_detach_skill(self, message):
        skill_id = message.data.get('skill_id')
        new_parsers = [
            p for p in self.engine.intent_parsers
            if not p.name.startswith(skill_id)
        ]
        self.engine.intent_parsers = new_parsers

    def handle_add_context(self, message):
        """ Add context

        Args:
            message: data contains the 'context' item to add
                     optionally can include 'word' to be injected as
                     an alias for the context item.
        """
        entity = {'confidence': 1.0}
        context = message.data.get('context')
        word = message.data.get('word') or ''
        origin = message.data.get('origin') or ''
        # if not a string type try creating a string from it
        if not isinstance(word, str):
            word = str(word)
        entity['data'] = [(word, context)]
        entity['match'] = word
        entity['key'] = word
        entity['origin'] = origin
        self.context_manager.inject_context(entity)

    def handle_remove_context(self, message):
        """ Remove specific context

        Args:
            message: data contains the 'context' item to remove
        """
        context = message.data.get('context')
        if context:
            self.context_manager.remove_context(context)

    def handle_clear_context(self, message):
        """ Clears all keywords from context """
        self.context_manager.clear_context()
Exemplo n.º 43
0
    "third eye blind", "the who", "the clash", "john mayer", "kings of leon",
    "adelle"
]

for a in artists:
    engine.register_entity(a, "Artist")

music_verbs = ["listen", "hear", "play"]

for mv in music_verbs:
    engine.register_entity(mv, "MusicVerb")

music_keywords = ["songs", "music"]

for mk in music_keywords:
    engine.register_entity(mk, "MusicKeyword")

music_intent = IntentBuilder("MusicIntent")\
    .require("MusicVerb")\
    .optionally("MusicKeyword")\
    .optionally("Artist")\
    .build()

engine.register_intent_parser(weather_intent)
engine.register_intent_parser(music_intent)

if __name__ == "__main__":
    for intent in engine.determine_intent(' '.join(sys.argv[1:])):
        if intent and intent.get('confidence') > 0:
            print(json.dumps(intent, indent=4))