diff --git a/ovos_adapt/opm.py b/ovos_adapt/opm.py index cfeb3d7..7d7aebd 100644 --- a/ovos_adapt/opm.py +++ b/ovos_adapt/opm.py @@ -15,17 +15,20 @@ """An intent parsing service using the Adapt parser.""" from functools import lru_cache from threading import Lock -from typing import List, Tuple, Optional +from typing import List, Optional, Iterable, Union, Dict from langcodes import closest_match +from ovos_bus_client.client import MessageBusClient from ovos_bus_client.message import Message -from ovos_bus_client.session import IntentContextManager as ContextManager, \ - SessionManager +from ovos_bus_client.session import SessionManager +from ovos_bus_client.util import get_message_lang from ovos_config.config import Configuration -from ovos_plugin_manager.templates.pipeline import IntentMatch, PipelinePlugin +from ovos_plugin_manager.templates.pipeline import IntentHandlerMatch, ConfidenceMatcherPipeline from ovos_utils import flatten_list +from ovos_utils.fakebus import FakeBus from ovos_utils.lang import standardize_lang_tag from ovos_utils.log import LOG +from ovos_workshop.intents import open_intent_envelope from ovos_adapt.engine import IntentDeterminationEngine @@ -45,13 +48,14 @@ def _entity_skill_id(skill_id): return skill_id -class AdaptPipeline(PipelinePlugin): +class AdaptPipeline(ConfidenceMatcherPipeline): """Intent service wrapping the Adapt intent Parser.""" - def __init__(self, config=None): + def __init__(self, bus: Optional[Union[MessageBusClient, FakeBus]] = None, + config: Optional[Dict] = None): core_config = Configuration() config = config or core_config.get("context", {}) # legacy mycroft-core path - super().__init__(config) + super().__init__(bus, config) self.lang = standardize_lang_tag(core_config.get("lang", "en-US")) langs = core_config.get('secondary_langs') or [] if self.lang not in langs: @@ -61,6 +65,7 @@ def __init__(self, config=None): for lang in langs} self.lock = Lock() + self.registered_vocab = [] self.max_words = 50 # if an utterance contains more words than this, don't attempt to match # TODO sanitize config option @@ -68,61 +73,14 @@ def __init__(self, config=None): self.conf_med = self.config.get("conf_med") or 0.45 self.conf_low = self.config.get("conf_low") or 0.25 - @property - def context_keywords(self): - LOG.warning( - "self.context_keywords has been deprecated and is unused, use self.config.get('keywords', []) instead") - return self.config.get('keywords', []) - - @context_keywords.setter - def context_keywords(self, val): - LOG.warning( - "self.context_keywords has been deprecated and is unused, edit mycroft.conf instead, setter will be ignored") - - @property - def context_max_frames(self): - LOG.warning( - "self.context_keywords has been deprecated and is unused, use self.config.get('max_frames', 3) instead") - return self.config.get('max_frames', 3) - - @context_max_frames.setter - def context_max_frames(self, val): - LOG.warning( - "self.context_max_frames has been deprecated and is unused, edit mycroft.conf instead, setter will be ignored") - - @property - def context_timeout(self): - LOG.warning("self.context_timeout has been deprecated and is unused, use self.config.get('timeout', 2) instead") - return self.config.get('timeout', 2) - - @context_timeout.setter - def context_timeout(self, val): - LOG.warning( - "self.context_timeout has been deprecated and is unused, edit mycroft.conf instead, setter will be ignored") - - @property - def context_greedy(self): - LOG.warning( - "self.context_greedy has been deprecated and is unused, use self.config.get('greedy', False) instead") - return self.config.get('greedy', False) + self.bus.on('register_vocab', self.handle_register_vocab) + self.bus.on('register_intent', self.handle_register_intent) + self.bus.on('detach_intent', self.handle_detach_intent) + self.bus.on('detach_skill', self.handle_detach_skill) - @context_greedy.setter - def context_greedy(self, val): - LOG.warning( - "self.context_greedy has been deprecated and is unused, edit mycroft.conf instead, setter will be ignored") - - @property - def context_manager(self): - LOG.warning("context_manager has been deprecated, use Session.context instead") - sess = SessionManager.get() - return sess.context - - @context_manager.setter - def context_manager(self, val): - LOG.warning("context_manager has been deprecated, use Session.context instead") - assert isinstance(val, ContextManager) - sess = SessionManager.get() - sess.context = val + self.bus.on('intent.service.adapt.get', self.handle_get_adapt) + self.bus.on('intent.service.adapt.manifest.get', self.handle_adapt_manifest) + self.bus.on('intent.service.adapt.vocab.manifest.get', self.handle_vocab_manifest) def update_context(self, intent): """Updates context with keyword from the intent. @@ -139,9 +97,7 @@ def update_context(self, intent): ents = [tag['entities'][0] for tag in intent['__tags__'] if 'entities' in tag] sess.context.update_context(ents) - def match_high(self, utterances: List[str], - lang: Optional[str] = None, - message: Optional[Message] = None): + def match_high(self, utterances: List[str], lang: str, message: Message) -> Optional[IntentHandlerMatch]: """Intent matcher for high confidence. Args: @@ -153,9 +109,7 @@ def match_high(self, utterances: List[str], return match return None - def match_medium(self, utterances: List[str], - lang: Optional[str] = None, - message: Optional[Message] = None): + def match_medium(self, utterances: List[str], lang: str, message: Message) -> Optional[IntentHandlerMatch]: """Intent matcher for medium confidence. Args: @@ -167,9 +121,7 @@ def match_medium(self, utterances: List[str], return match return None - def match_low(self, utterances: List[str], - lang: Optional[str] = None, - message: Optional[Message] = None): + def match_low(self, utterances: List[str], lang: str, message: Message) -> Optional[IntentHandlerMatch]: """Intent matcher for low confidence. Args: @@ -182,7 +134,7 @@ def match_low(self, utterances: List[str], return None @lru_cache(maxsize=3) # NOTE - message is a string because of this - def match_intent(self, utterances: Tuple[str], + def match_intent(self, utterances: Iterable[str], lang: Optional[str] = None, message: Optional[str] = None): """Run the Adapt engine to search for an matching intent. @@ -252,9 +204,10 @@ def take_best(intent, utt): sess.context.update_context(ents) skill_id = best_intent['intent_type'].split(":")[0] - ret = IntentMatch( - 'Adapt', best_intent['intent_type'], best_intent, skill_id, - best_intent['utterance'] + ret = IntentHandlerMatch( + match_type=best_intent['intent_type'], + match_data=best_intent, skill_id=skill_id, + utterance=best_intent['utterance'] ) else: ret = None @@ -365,3 +318,81 @@ def shutdown(self): for lang in self.engines: parsers = self.engines[lang].intent_parsers self.engines[lang].drop_intent_parser(parsers) + + @property + def registered_intents(self): + lang = get_message_lang() + return [parser.__dict__ for parser in self.engines[lang].intent_parsers] + + def handle_register_vocab(self, message): + """Register adapt vocabulary. + + Args: + message (Message): message containing vocab info + """ + entity_value = message.data.get('entity_value') + entity_type = message.data.get('entity_type') + regex_str = message.data.get('regex') + alias_of = message.data.get('alias_of') + lang = get_message_lang(message) + self.register_vocabulary(entity_value, entity_type, + alias_of, regex_str, lang) + self.registered_vocab.append(message.data) + + def handle_register_intent(self, message): + """Register adapt intent. + + Args: + message (Message): message containing intent info + """ + intent = open_intent_envelope(message) + self.register_intent(intent) + + def handle_detach_intent(self, message): + """Remover adapt intent. + + Args: + message (Message): message containing intent info + """ + intent_name = message.data.get('intent_name') + self.detach_intent(intent_name) + + def handle_detach_skill(self, message): + """Remove all intents registered for a specific skill. + + Args: + message (Message): message containing intent info + """ + skill_id = message.data.get('skill_id') + self.detach_skill(skill_id) + + def handle_get_adapt(self, message: Message): + """handler getting the adapt response for an utterance. + + Args: + message (Message): message containing utterance + """ + utterance = message.data["utterance"] + lang = get_message_lang(message) + intent = self.match_intent((utterance,), lang, message.serialize()) + intent_data = intent.intent_data if intent else None + self.bus.emit(message.reply("intent.service.adapt.reply", + {"intent": intent_data})) + + def handle_adapt_manifest(self, message): + """Send adapt intent manifest to caller. + + Argument: + message: query message to reply to. + """ + self.bus.emit(message.reply("intent.service.adapt.manifest", + {"intents": self.registered_intents})) + + def handle_vocab_manifest(self, message): + """Send adapt vocabulary manifest to caller. + + Argument: + message: query message to reply to. + """ + self.bus.emit(message.reply("intent.service.adapt.vocab.manifest", + {"vocab": self.registered_vocab})) diff --git a/requirements.txt b/requirements.txt index 6673854..380568c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ six>=1.10.0 -ovos-plugin-manager>=0.0.26,<1.0.0 +ovos-plugin-manager>=0.5.0,<1.0.0 ovos-workshop>=0.1.7,<2.0.0 ovos-utils>=0.3.4,<1.0.0 langcodes \ No newline at end of file