16 from adapt.context
import ContextManagerFrame
17 from adapt.engine
import IntentDeterminationEngine
18 from adapt.intent
import IntentBuilder
36 """ Handle Adapt issue with context injection combined with one_of. 38 For all entries in the intent result where the value is None try to 39 populate using a value from the __tags__ structure. 41 for key
in best_intent:
42 if best_intent[key]
is None:
43 for t
in best_intent[
'__tags__']:
45 best_intent[key] = t[key][0][
'entities'][0][
'key']
52 Use to track context throughout the course of a conversational session. 53 How to manage a session's lifecycle is not captured here. 65 if context_id
in f.entities[0].
get(
'data', [])]
70 entity(object): Format example... 71 {'data': 'Entity tag as <str>', 72 'key': 'entity proper name as <str>', 73 'confidence': <float>' 75 metadata(object): dict, arbitrary metadata about entity injected 77 metadata = metadata
or {}
83 if top_frame
and top_frame[0].metadata_matches(metadata):
84 top_frame[0].merge_context(entity, metadata)
86 frame = ContextManagerFrame(entities=[entity],
87 metadata=metadata.copy())
88 self.frame_stack.insert(0, (frame, time.time()))
89 except (IndexError, KeyError):
92 def get_context(self, max_frames=None, missing_entities=None):
93 """ Constructs a list of entities from the context. 96 max_frames(int): maximum number of frames to look back 97 missing_entities(list of str): a list or set of tag names, 101 list: a list of entities 103 missing_entities = missing_entities
or []
105 relevant_frames = [frame[0]
for frame
in self.
frame_stack if 106 time.time() - frame[1] < self.
timeout]
107 if not max_frames
or max_frames > len(relevant_frames):
108 max_frames = len(relevant_frames)
110 missing_entities = list(missing_entities)
114 for i
in range(max_frames):
115 frame_entities = [entity.copy()
for entity
in 116 relevant_frames[i].entities]
117 for entity
in frame_entities:
118 entity[
'confidence'] = entity.get(
'confidence', 1.0) \
120 context += frame_entities
123 if entity[
'origin'] != last
or entity[
'origin'] ==
'':
125 last = entity[
'origin']
129 if len(missing_entities) > 0:
130 for entity
in context:
131 if entity.get(
'data')
in missing_entities:
132 result.append(entity)
137 missing_entities.remove(entity.get(
'data'))
145 keyword = f[
'data'][0][1]
146 if keyword
not in processed:
148 processed.append(keyword)
156 self.
engine = IntentDeterminationEngine()
179 self.bus.on(
'mycroft.speech.recognition.unknown', self.
reset_converse)
182 def add_active_skill_handler(message):
184 self.bus.on(
'active_skill_request', add_active_skill_handler)
193 Messagebus handler, updates dictionary of if to skill name 196 self.
skill_names[message.data[
'id']] = message.data[
'name']
199 """ Get skill name from skill ID. 202 skill_id: a skill id as encoded in Intent handlers. 205 (str) Skill name or the skill id if the skill wasn't found 207 return self.skill_names.get(skill_id, skill_id)
210 """Let skills know there was a problem with speech recognition""" 211 lang = message.data.get(
'lang',
"en-us")
220 self.bus.emit(
Message(
"skill.converse.request", {
221 "skill_id": skill_id,
"utterances": utterances,
"lang": lang}))
222 start_time = time.time()
225 t = time.time() - start_time
232 skill_id = message.data[
"skill_id"]
233 if message.data[
"error"] ==
"skill id does not exist":
240 skill_id = message.data[
"skill_id"]
247 if skill[0] == skill_id:
248 self.active_skills.remove(skill)
255 self.active_skills.insert(0, [skill_id, time.time()])
258 """ Updates context with keyword from the intent. 260 NOTE: This method currently won't handle one_of intent keywords 261 since it's not using quite the same format as other intent 262 keywords. This is under investigation in adapt, PR pending. 265 intent: Intent to scan for keywords 267 for tag
in intent[
'__tags__']:
268 if 'entities' not in tag:
270 context_entity = tag[
'entities'][0]
272 self.context_manager.inject_context(context_entity)
274 self.context_manager.inject_context(context_entity)
278 Send timing metrics to the backend. 280 NOTE: This only applies to those with Opt In. 282 ident = context[
'ident']
if 'ident' in context
else None 285 parts = intent.get(
'intent_type',
'').split(
':')
288 intent_type =
':'.join([intent_type] + parts[1:])
290 {
'intent_type': intent_type})
293 {
'intent_type':
'intent_failure'})
296 """ Main entrypoint for handling user utterances with Mycroft skills 298 Monitor the messagebus for 'recognizer_loop:utterance', typically 299 generated by a spoken interaction but potentially also from a CLI 300 or other method of injecting a 'user utterance' into the system. 302 Utterances then work through this sequence to be handled: 303 1) Active skills attempt to handle using converse() 304 2) Padatious high match intents (conf > 0.95) 305 3) Adapt intent handlers 307 - Padatious near match intents (conf > 0.8) 309 - Padatious loose match intents (conf > 0.5) 310 - Unknown intent handler 313 message (Message): The messagebus data 317 lang = message.data.get(
'lang',
"en-us")
320 utterances = message.data.get(
'utterances', [])
322 norm_utterances = [
normalize(u.lower(), remove_articles=
False)
327 combined = utterances + list(set(norm_utterances) -
329 LOG.debug(
"Utterances: {}".format(combined))
333 padatious_intent =
None 336 converse = self.
_converse(combined, lang)
341 norm_utterances, lang)
343 _intent = PadatiousService.instance.calc_intent(utt)
345 best = padatious_intent.conf
if padatious_intent\
347 if best < _intent.conf:
348 padatious_intent = _intent
349 LOG.debug(
"Padatious intent: {}".format(padatious_intent))
350 LOG.debug(
" Adapt intent: {}".format(intent))
354 LOG.debug(
"Handled in converse()")
355 ident = message.context[
'ident']
if message.context
else None 357 {
'intent_type':
'converse'})
359 elif (intent
and intent.get(
'confidence', 0.0) > 0.0
and 360 not (padatious_intent
and padatious_intent.conf >= 0.95)):
365 skill_id = intent[
'intent_type'].split(
":")[0]
372 LOG.error(
'Error during workaround_one_of_context')
373 reply = message.reply(intent.get(
'intent_type'), intent)
379 reply = message.reply(
'intent_failure',
380 {
'utterance': utterances[0],
381 'norm_utt': norm_utterances[0],
385 except Exception
as e:
389 """ Give active skills a chance at the utterance 392 utterances (list): list of utterances 393 lang (string): 4 letter ISO language code 396 bool: True if converse handled it, False if no skill processes it 401 if time.time() - skill[
414 """ Run the Adapt engine to search for an matching intent 417 raw_utt (list): list of utterances 418 norm_utt (list): same list of utterances, normalized 419 lang (string): language code, e.g "en-us" 422 Intent structure, or None if no match was found. 426 def take_best(intent, utt):
428 best = best_intent.get(
'confidence', 0.0)
if best_intent
else 0.0
429 conf = intent.get(
'confidence', 0.0)
433 best_intent[
'utterance'] = utt
435 for idx, utt
in enumerate(raw_utt):
437 intents = [i
for i
in self.engine.determine_intent(
442 take_best(intents[0], utt)
446 norm_intents = [i
for i
in self.engine.determine_intent(
451 take_best(norm_intents[0], utt)
452 except Exception
as e:
457 start_concept = message.data.get(
'start')
458 end_concept = message.data.get(
'end')
459 regex_str = message.data.get(
'regex')
460 alias_of = message.data.get(
'alias_of')
462 self.engine.register_regex_entity(regex_str)
464 self.engine.register_entity(
465 start_concept, end_concept, alias_of=alias_of)
469 self.engine.register_intent_parser(intent)
472 intent_name = message.data.get(
'intent_name')
474 p
for p
in self.engine.intent_parsers
if p.name != intent_name]
475 self.engine.intent_parsers = new_parsers
478 skill_id = message.data.get(
'skill_id')
480 p
for p
in self.engine.intent_parsers
if 481 not p.name.startswith(skill_id)]
482 self.engine.intent_parsers = new_parsers
488 message: data contains the 'context' item to add 489 optionally can include 'word' to be injected as 490 an alias for the context item. 492 entity = {
'confidence': 1.0}
493 context = message.data.get(
'context')
494 word = message.data.get(
'word')
or '' 495 origin = message.data.get(
'origin')
or '' 497 if not isinstance(word, str):
499 entity[
'data'] = [(word, context)]
500 entity[
'match'] = word
502 entity[
'origin'] = origin
503 self.context_manager.inject_context(entity)
506 """ Remove specific context 509 message: data contains the 'context' item to remove 511 context = message.data.get(
'context')
513 self.context_manager.remove_context(context)
516 """ Clears all keywords from context """ 517 self.context_manager.clear_context()
def add_active_skill(self, skill_id)
def report_timing(ident, system, timing, additional_data=None)
def __init__(self, timeout)
def remove_active_skill(self, skill_id)
def workaround_one_of_context(best_intent)
def update_skill_name_dict(self, message)
def open_intent_envelope(message)
def reset_converse(self, message)
def set_active_lang(lang_code)
def handle_register_vocab(self, message)
def handle_detach_intent(self, message)
def handle_add_context(self, message)
def handle_converse_response(self, message)
def remove_context(self, context_id)
def update_context(self, intent)
def handle_utterance(self, message)
def handle_clear_context(self, message)
def handle_register_intent(self, message)
def _converse(self, utterances, lang)
def handle_converse_error(self, message)
def __init__(self, name='')
def send_metrics(self, intent, context, stopwatch)
def get_skill_name(self, skill_id)
def get_context(self, max_frames=None, missing_entities=None)
def handle_detach_skill(self, message)
def _adapt_intent_match(self, raw_utt, norm_utt, lang)
def handle_remove_context(self, message)
def inject_context(self, entity, metadata=None)
def do_converse(self, utterances, skill_id, lang)
def get(phrase, lang=None, context=None)
def normalize(text, lang=None, remove_articles=True)