2019-07-19 10:29:05 +00:00
|
|
|
# Copyright 2019 Mycroft AI Inc.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
#
|
2019-07-22 07:11:34 +00:00
|
|
|
"""The fallback skill implements a special type of skill handling
|
2019-07-19 10:29:05 +00:00
|
|
|
utterances not handled by the intent system.
|
|
|
|
"""
|
|
|
|
import operator
|
|
|
|
from mycroft.metrics import report_timing, Stopwatch
|
|
|
|
from mycroft.util.log import LOG
|
|
|
|
|
|
|
|
|
|
|
|
from .mycroft_skill import MycroftSkill, get_handler_name
|
|
|
|
|
|
|
|
|
|
|
|
class FallbackSkill(MycroftSkill):
|
2019-07-22 07:11:34 +00:00
|
|
|
"""Fallbacks come into play when no skill matches an Adapt or closely with
|
|
|
|
a Padatious intent. All Fallback skills work together to give them a
|
|
|
|
view of the user's utterance. Fallback handlers are called in an order
|
|
|
|
determined the priority provided when the the handler is registered.
|
|
|
|
|
|
|
|
======== ======== ================================================
|
|
|
|
Priority Who? Purpose
|
|
|
|
======== ======== ================================================
|
|
|
|
1-4 RESERVED Unused for now, slot for pre-Padatious if needed
|
|
|
|
5 MYCROFT Padatious near match (conf > 0.8)
|
|
|
|
6-88 USER General
|
|
|
|
89 MYCROFT Padatious loose match (conf > 0.5)
|
|
|
|
90-99 USER Uncaught intents
|
|
|
|
100+ MYCROFT Fallback Unknown or other future use
|
|
|
|
======== ======== ================================================
|
|
|
|
|
|
|
|
Handlers with the numerically lowest priority are invoked first.
|
|
|
|
Multiple fallbacks can exist at the same priority, but no order is
|
|
|
|
guaranteed.
|
|
|
|
|
|
|
|
A Fallback can either observe or consume an utterance. A consumed
|
|
|
|
utterance will not be see by any other Fallback handlers.
|
2019-07-19 10:29:05 +00:00
|
|
|
"""
|
|
|
|
fallback_handlers = {}
|
|
|
|
|
|
|
|
def __init__(self, name=None, bus=None, use_settings=True):
|
2019-07-22 07:24:36 +00:00
|
|
|
super().__init__(name, bus, use_settings)
|
2019-07-19 10:29:05 +00:00
|
|
|
|
|
|
|
# list of fallback handlers registered by this instance
|
|
|
|
self.instance_fallback_handlers = []
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def make_intent_failure_handler(cls, bus):
|
|
|
|
"""Goes through all fallback handlers until one returns True"""
|
|
|
|
|
|
|
|
def handler(message):
|
|
|
|
# indicate fallback handling start
|
2019-10-28 05:48:44 +00:00
|
|
|
bus.emit(message.forward("mycroft.skill.handler.start",
|
|
|
|
data={'handler': "fallback"}))
|
2019-07-19 10:29:05 +00:00
|
|
|
|
|
|
|
stopwatch = Stopwatch()
|
|
|
|
handler_name = None
|
|
|
|
with stopwatch:
|
|
|
|
for _, handler in sorted(cls.fallback_handlers.items(),
|
|
|
|
key=operator.itemgetter(0)):
|
|
|
|
try:
|
|
|
|
if handler(message):
|
|
|
|
# indicate completion
|
|
|
|
handler_name = get_handler_name(handler)
|
2019-10-28 05:48:44 +00:00
|
|
|
bus.emit(message.forward(
|
2019-07-19 10:29:05 +00:00
|
|
|
'mycroft.skill.handler.complete',
|
|
|
|
data={'handler': "fallback",
|
|
|
|
"fallback_handler": handler_name}))
|
|
|
|
break
|
|
|
|
except Exception:
|
|
|
|
LOG.exception('Exception in fallback.')
|
|
|
|
else: # No fallback could handle the utterance
|
2019-10-28 05:48:44 +00:00
|
|
|
bus.emit(message.forward('complete_intent_failure'))
|
2019-07-19 10:29:05 +00:00
|
|
|
warning = "No fallback could handle intent."
|
|
|
|
LOG.warning(warning)
|
|
|
|
# indicate completion with exception
|
2019-10-28 05:48:44 +00:00
|
|
|
bus.emit(message.forward('mycroft.skill.handler.complete',
|
|
|
|
data={'handler': "fallback",
|
|
|
|
'exception': warning}))
|
2019-07-19 10:29:05 +00:00
|
|
|
|
|
|
|
# Send timing metric
|
|
|
|
if message.context.get('ident'):
|
|
|
|
ident = message.context['ident']
|
|
|
|
report_timing(ident, 'fallback_handler', stopwatch,
|
|
|
|
{'handler': handler_name})
|
|
|
|
|
|
|
|
return handler
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _register_fallback(cls, handler, priority):
|
2019-07-22 07:11:34 +00:00
|
|
|
"""Register a function to be called as a general info fallback
|
2019-07-19 10:29:05 +00:00
|
|
|
Fallback should receive message and return
|
|
|
|
a boolean (True if succeeded or False if failed)
|
|
|
|
|
|
|
|
Lower priority gets run first
|
|
|
|
0 for high priority 100 for low priority
|
|
|
|
"""
|
|
|
|
while priority in cls.fallback_handlers:
|
|
|
|
priority += 1
|
|
|
|
|
|
|
|
cls.fallback_handlers[priority] = handler
|
|
|
|
|
|
|
|
def register_fallback(self, handler, priority):
|
2019-07-22 07:11:34 +00:00
|
|
|
"""Register a fallback with the list of fallback handlers and with the
|
|
|
|
list of handlers registered by this instance
|
2019-07-19 10:29:05 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
if handler(*args, **kwargs):
|
|
|
|
self.make_active()
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
self.instance_fallback_handlers.append(wrapper)
|
|
|
|
self._register_fallback(wrapper, priority)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def remove_fallback(cls, handler_to_del):
|
2019-07-22 07:11:34 +00:00
|
|
|
"""Remove a fallback handler.
|
2019-07-19 10:29:05 +00:00
|
|
|
|
2019-07-22 07:11:34 +00:00
|
|
|
Arguments:
|
|
|
|
handler_to_del: reference to handler
|
2019-07-19 10:29:05 +00:00
|
|
|
"""
|
|
|
|
for priority, handler in cls.fallback_handlers.items():
|
|
|
|
if handler == handler_to_del:
|
|
|
|
del cls.fallback_handlers[priority]
|
|
|
|
return
|
|
|
|
LOG.warning('Could not remove fallback!')
|
|
|
|
|
|
|
|
def remove_instance_handlers(self):
|
2019-07-22 07:11:34 +00:00
|
|
|
"""Remove all fallback handlers registered by the fallback skill."""
|
2019-07-19 10:29:05 +00:00
|
|
|
while len(self.instance_fallback_handlers):
|
|
|
|
handler = self.instance_fallback_handlers.pop()
|
|
|
|
self.remove_fallback(handler)
|
|
|
|
|
|
|
|
def default_shutdown(self):
|
2019-07-22 07:11:34 +00:00
|
|
|
"""Remove all registered handlers and perform skill shutdown."""
|
2019-07-19 10:29:05 +00:00
|
|
|
self.remove_instance_handlers()
|
|
|
|
super(FallbackSkill, self).default_shutdown()
|