From 70a858d14e7cdf3fb2bfecd0affdf1060b15d91a Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 20 Sep 2023 02:13:00 +0100 Subject: [PATCH 1/7] feat/pipeline_plugins --- ovos_plugin_manager/templates/pipeline.py | 60 +++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 ovos_plugin_manager/templates/pipeline.py diff --git a/ovos_plugin_manager/templates/pipeline.py b/ovos_plugin_manager/templates/pipeline.py new file mode 100644 index 00000000..8ca7fea3 --- /dev/null +++ b/ovos_plugin_manager/templates/pipeline.py @@ -0,0 +1,60 @@ +import abc +from collections import namedtuple + +from ovos_config import Configuration + +from ovos_bus_client.message import Message + +from ovos_utils import classproperty + + +# Intent match response tuple containing +# intent_service: Name of the service that matched the intent +# intent_type: intent name (used to call intent handler over the message bus) +# intent_data: data provided by the intent match +# skill_id: the skill this handler belongs to +IntentMatch = namedtuple('IntentMatch', + ['intent_service', 'intent_type', + 'intent_data', 'skill_id', 'utterance'] + ) + + +class PipelineComponentPlugin: + + def __init__(self, bus, config=None): + self.config = config or \ + Configuration().get("pipeline", {}).get(self.matcher_id) + self.bus = bus + self.register_bus_events() + + @classproperty + def matcher_id(self): + raise NotImplementedError + + def register_bus_events(self): + pass + + @abc.abstractmethod + def match(self, utterances: list, lang: str, message: Message) -> IntentMatch: + pass + + def shutdown(self): + pass + + +class PipelineMultiConfPlugin(PipelineComponentPlugin): + + def match(self, utterances: list, lang: str, message: Message): + return self.match_high(utterances, lang, message) + + @abc.abstractmethod + def match_high(self, utterances: list, lang: str, message: Message) -> IntentMatch: + pass + + @abc.abstractmethod + def match_medium(self, utterances: list, lang: str, message: Message) -> IntentMatch: + pass + + @abc.abstractmethod + def match_low(self, utterances: list, lang: str, message: Message) -> IntentMatch: + pass From b940dad91450a633bf1bedb53fc102b24f9391da Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 20 Sep 2023 15:40:01 +0100 Subject: [PATCH 2/7] feat/pipeline_plugins formalize intent base class --- ovos_plugin_manager/templates/pipeline.py | 334 ++++++++++++++++++++-- 1 file changed, 312 insertions(+), 22 deletions(-) diff --git a/ovos_plugin_manager/templates/pipeline.py b/ovos_plugin_manager/templates/pipeline.py index 8ca7fea3..fe793481 100644 --- a/ovos_plugin_manager/templates/pipeline.py +++ b/ovos_plugin_manager/templates/pipeline.py @@ -1,25 +1,27 @@ import abc -from collections import namedtuple +import re +from dataclasses import dataclass from ovos_config import Configuration from ovos_bus_client.message import Message - from ovos_utils import classproperty +from ovos_utils.messagebus import get_message_lang -# Intent match response tuple containing -# intent_service: Name of the service that matched the intent -# intent_type: intent name (used to call intent handler over the message bus) -# intent_data: data provided by the intent match -# skill_id: the skill this handler belongs to -IntentMatch = namedtuple('IntentMatch', - ['intent_service', 'intent_type', - 'intent_data', 'skill_id', 'utterance'] - ) +@dataclass() +class IntentMatch: # replaces the named tuple from classic mycroft + intent_service: str + intent_type: str + intent_data: dict + skill_id: str + utterance: str + confidence: float = 0.0 + utterance_remainder: str = "" # unconsumed text / leftover utterance -class PipelineComponentPlugin: +class PipelinePlugin: + """these plugins return a match to the utterance, but do not trigger an action """ def __init__(self, bus, config=None): self.config = config or \ @@ -27,6 +29,12 @@ def __init__(self, bus, config=None): self.bus = bus self.register_bus_events() + @property # magic property - take session data into account + def lang(self): + return get_message_lang() or \ + self.config.get("lang") or \ + "en-us" + @classproperty def matcher_id(self): raise NotImplementedError @@ -41,20 +49,302 @@ def match(self, utterances: list, lang: str, message: Message) -> IntentMatch: def shutdown(self): pass + # helpers to filter matches by confidence + # exposed in pipeline config under mycroft.conf as {matcher_id}_high/medium/low + def match_high(self, utterances: list, lang: str, message: Message) -> IntentMatch: + thresh = 0.9 # TODO - from config + match = self.match(utterances, lang, message) + if match.confidence >= thresh: + return match -class PipelineMultiConfPlugin(PipelineComponentPlugin): + def match_medium(self, utterances: list, lang: str, message: Message) -> IntentMatch: + thresh = 0.75 # TODO - from config + match = self.match(utterances, lang, message) + if match.confidence >= thresh: + return match - def match(self, utterances: list, lang: str, message: Message): - return self.match_high(utterances, lang, message) + def match_low(self, utterances: list, lang: str, message: Message) -> IntentMatch: + thresh = 0.5 # TODO - from config + match = self.match(utterances, lang, message) + if match.confidence >= thresh: + return match - @abc.abstractmethod - def match_high(self, utterances: list, lang: str, message: Message) -> IntentMatch: - pass - @abc.abstractmethod - def match_medium(self, utterances: list, lang: str, message: Message) -> IntentMatch: - pass +class PipelineStagePlugin(PipelinePlugin): + """WARNING: has side effects when match is used + + these plugins will consume an utterance during the match process, + aborting the next pipeline stages if a match is returned. + + it is not known if this component will match without going through the match process + + eg. converse, fallback """ + + +class IntentPipelinePlugin(PipelinePlugin): + + def __init__(self, bus, config=None): + super().__init__(bus, config) + self.registered_intents = [] + self.registered_entities = [] + self.register_intent_bus_handlers() + + def register_intent_bus_handlers(self): + # WIP WIP WIP WIP + # TODO - intent registering messages + self.bus.on('detach_intent', self.handle_detach_intent) + self.bus.on('detach_skill', self.handle_detach_skill) + + # backwards compat handlers with adapt/padatious namespace + # TODO - deprecate in 0.1.0 + self.bus.on('padatious:register_intent', self.handle_register_intent) + self.bus.on('padatious:register_entity', self.handle_register_entity) + self.bus.on('register_vocab', self.handle_register_vocab) + self.bus.on('register_intent', self.handle_register_keyword_intent) + + # default bus handlers + def handle_register_vocab(self, message): + """Register adapt-like vocabulary. + + This will handle both regex registration and registration of normal + keywords. if the "regex_str" argument is set all other arguments will + be ignored. + + message.data: + entity_value: the natural language word + samples: list of entity_values + regex: a regex pattern to extract the entity with + entity_type: the type/tag of an entity instance + alias_of: entity this is an alternative for + + Args: + message (Message): message containing vocab info + """ + skill_id = message.data.get("skill_id") or message.context.get("skill_id") + is_r = False + if "samples" in message.data: + samples = message.data["samples"] + elif "regex" in message.data: + samples = [message.data["regex"]] + is_r = True + else: + entity_value = message.data.get('entity_value') + samples = [entity_value] + + entity_type = message.data.get('entity_type') + + alias_of = message.data.get('alias_of') + + if is_r: # regex + self.register_regex_entity(skill_id=skill_id, + entity_name=entity_type, + samples=samples, + lang=self.lang) + if alias_of: + self.register_regex_entity(skill_id=skill_id, + entity_name=alias_of, + samples=samples, + lang=self.lang) + + else: + self.register_entity(skill_id=skill_id, + entity_name=entity_type, + samples=samples, + lang=self.lang) + if alias_of: + self.register_entity(skill_id=skill_id, + entity_name=alias_of, + samples=samples, + lang=self.lang) + + def handle_detach_entity(self, message): + skill_id = message.data["skill_id"] + entity_name = message.data["entity_name"] + self.detach_entity(skill_id, entity_name) + self.train() + + def handle_detach_intent(self, message): + skill_id = message.data["skill_id"] + intent_name = message.data["intent_name"] + self.detach_intent(skill_id, intent_name) + self.train() + + def handle_detach_skill(self, message): + skill_id = message.data["skill_id"] + self.detach_skill(skill_id) + self.train() + # helpers to navigate registered intent data + @property + def manifest(self): + # TODO - munge/unmmunge skill_id in name ? + return { + "intent_names": [e.name for e in self.registered_intents + if isinstance(e, IntentDefinition)], + "keyword_intent_names": [e.name for e in self.registered_intents + if isinstance(e, KeywordIntentDefinition)], + "patterns": [e.name for e in self.registered_intents + if isinstance(e, RegexIntentDefinition)], + "entities": [e.name for e in self.registered_entities + if isinstance(e, EntityDefinition)], + "entity_patterns": [e.name for e in self.registered_entities + if isinstance(e, RegexEntityDefinition)], + } + + def get_intent_samples(self, skill_id, intent_name, lang=None): + lang = lang or self.lang + for e in [e for e in self.registered_intents if isinstance(e, IntentDefinition)]: + if e.name == intent_name and e.lang == lang and e.skill_id == skill_id: + return e.samples + return [] + + def get_entity_samples(self, skill_id, entity_name, lang=None): + lang = lang or self.lang + for e in [e for e in self.registered_entities if isinstance(e, EntityDefinition)]: + if e.name == entity_name and e.lang == lang and e.skill_id == skill_id: + return e.samples + return [] + + # registering/unloading intents + def detach_skill(self, skill_id): + to_detach = [intent for intent in self.registered_intents if intent.skill_id == skill_id] + \ + [entity for entity in self.registered_entities if entity.skill_id == skill_id] + self.registered_entities = [e for e in self.registered_entities + if e not in to_detach] + self.registered_intents = [e for e in self.registered_intents + if e not in to_detach] + + def detach_entity(self, skill_id, entity_name): + self.registered_entities = [e for e in self.registered_entities + if e.name != entity_name or e.skill_id != skill_id] + + def detach_intent(self, skill_id, intent_name): + self.registered_intents = [e for e in self.registered_intents + if e.name != intent_name or e.skill_id != skill_id] + + def register_entity(self, skill_id, entity_name, samples, lang=None): + lang = lang or self.lang + for ent in self.registered_entities: + if not isinstance(ent, RegexEntityDefinition) and \ + ent.name == entity_name and \ + ent.skill_id == skill_id and \ + ent.lang == lang: + # merge new samples, if not wanted detach the entity before re-registering it + ent.samples = ent.samples + samples + break + else: + entity = EntityDefinition(entity_name, lang=lang, samples=samples, skill_id=skill_id) + self.registered_entities.append(entity) + + def register_intent(self, skill_id, intent_name, samples, lang=None): + lang = lang or self.lang + + for ent in self.registered_entities: + if not isinstance(ent, RegexIntentDefinition) and \ + ent.name == intent_name and \ + ent.skill_id == skill_id and \ + ent.lang == lang: + # merge new samples, if not wanted detach the intent before re-registering it + ent.samples = ent.samples + samples + break + else: + intent = IntentDefinition(intent_name, lang=lang, samples=samples, skill_id=skill_id) + self.registered_intents.append(intent) + + def register_keyword_intent(self, skill_id, intent_name, keywords, + optional=None, at_least_one=None, + excluded=None, lang=None): + lang = lang or self.lang + intent = KeywordIntentDefinition(intent_name, lang=lang, skill_id=skill_id, + requires=keywords, optional=optional, + at_least_one=at_least_one, excluded=excluded) + # NOTE - no merging here, we allow multiple variations of same intent with different rules to match + self.registered_intents.append(intent) + + def register_regex_entity(self, skill_id, entity_name, samples, + lang=None): + lang = lang or self.lang + entity = RegexEntityDefinition(entity_name, lang=lang, skill_id=skill_id, + patterns=[re.compile(pattern) for pattern in samples]) + self.registered_entities.append(entity) + + def register_regex_intent(self, skill_id, intent_name, samples, + lang=None): + lang = lang or self.lang + intent = RegexIntentDefinition(intent_name, lang=lang, skill_id=skill_id, + patterns=[re.compile(pattern) for pattern in samples]) + self.registered_intents.append(intent) + + # from_file helper methods + def register_entity_from_file(self, skill_id, entity_name, file_name, + lang=None): + with open(file_name) as f: + entities = f.read().split("\n") + self.register_entity(skill_id, entity_name, entities, + lang=lang) + + def register_intent_from_file(self, skill_id, intent_name, file_name, + lang=None): + with open(file_name) as f: + intents = f.read().split("\n") + self.register_intent(skill_id, intent_name, intents, + lang=lang) + + def register_regex_entity_from_file(self, skill_id, entity_name, file_name, + lang=None): + with open(file_name) as f: + entities = f.read().split("\n") + self.register_regex_entity(skill_id, entity_name, entities, + lang=lang) + + def register_regex_intent_from_file(self, skill_id, intent_name, file_name, + lang=None): + with open(file_name) as f: + intents = f.read().split("\n") + self.register_regex_intent(skill_id, intent_name, intents, + lang=lang) + + # intent plugins api @abc.abstractmethod - def match_low(self, utterances: list, lang: str, message: Message) -> IntentMatch: + def train(self): + """ plugins should parse self.registered_intents and self.registered_entities here and handle any new entries + + must be callable multiple times + + this is called on mycroft.ready and then after every new skill loads/unloads""" pass + + +@dataclass() +class BaseDefinition: + name: str + lang: str + skill_id: str + + +@dataclass() +class EntityDefinition(BaseDefinition): + samples: list + + +@dataclass() +class IntentDefinition(BaseDefinition): + samples: list + + +@dataclass() +class RegexEntityDefinition(BaseDefinition): + patterns: list + + +@dataclass() +class RegexIntentDefinition(BaseDefinition): + patterns: list + + +@dataclass() +class KeywordIntentDefinition(BaseDefinition): + requires: list + optional: list + excluded: list + at_least_one: list From 40ea0850be0165427f455b73c88e4eb2990f5ad4 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 20 Sep 2023 16:23:54 +0100 Subject: [PATCH 3/7] more bus handlers --- ovos_plugin_manager/templates/pipeline.py | 100 ++++++++++++++++------ 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/ovos_plugin_manager/templates/pipeline.py b/ovos_plugin_manager/templates/pipeline.py index fe793481..fe372706 100644 --- a/ovos_plugin_manager/templates/pipeline.py +++ b/ovos_plugin_manager/templates/pipeline.py @@ -91,19 +91,82 @@ def __init__(self, bus, config=None): def register_intent_bus_handlers(self): # WIP WIP WIP WIP - # TODO - intent registering messages - self.bus.on('detach_intent', self.handle_detach_intent) - self.bus.on('detach_skill', self.handle_detach_skill) + self.bus.on('intent.service:detach_intent', self.handle_detach_intent) + self.bus.on('intent.service:detach_entity', self.handle_detach_entity) + self.bus.on('intent.service:detach_skill', self.handle_detach_skill) + self.bus.on('intent.service.register_intent', self.handle_register_intent) + self.bus.on('intent.service.register_keyword_intent', self.handle_register_keyword_intent) + self.bus.on('intent.service.register_regex_intent', self.handle_register_regex_intent) + self.bus.on('intent.service:register_entity', self.handle_register_entity) + self.bus.on('intent.service:register_regex_entity', self.handle_register_regex_entity) # backwards compat handlers with adapt/padatious namespace - # TODO - deprecate in 0.1.0 - self.bus.on('padatious:register_intent', self.handle_register_intent) - self.bus.on('padatious:register_entity', self.handle_register_entity) - self.bus.on('register_vocab', self.handle_register_vocab) - self.bus.on('register_intent', self.handle_register_keyword_intent) + # TODO - deprecate in ovos-core 0.1.0 + self.bus.on('padatious:register_intent', self._handle_padatious_intent) + self.bus.on('padatious:register_entity', self._handle_padatious_entity) + self.bus.on('register_vocab', self._handle_adapt_vocab) + self.bus.on('register_intent', self._handle_adapt_intent) + self.bus.on('detach_intent', self._handle_detach_intent) + self.bus.on('detach_skill', self._handle_detach_skill) # default bus handlers - def handle_register_vocab(self, message): + def handle_register_entity(self, message): + """Register entities. + + message.data: + samples: list of natural language words / entity examples + name: the type/tag of an entity instance + + Args: + message (Message): message containing vocab info + """ + skill_id = message.data.get("skill_id") or message.context.get("skill_id") + samples = message.data["samples"] + entity_type = message.data.get('name') + + self.register_entity(skill_id=skill_id, + entity_name=entity_type, + samples=samples, + lang=self.lang) + + def handle_register_regex_entity(self, message): + """Register regex entities. + + message.data: + samples: list of regex expressions that extract the entity + name: the type/tag of an entity instance + + Args: + message (Message): message containing vocab info + """ + skill_id = message.data.get("skill_id") or message.context.get("skill_id") + samples = message.data["samples"] + entity_type = message.data.get('name') + + self.register_regex_entity(skill_id=skill_id, + entity_name=entity_type, + samples=samples, + lang=self.lang) + + def handle_detach_entity(self, message): + skill_id = message.data["skill_id"] + entity_name = message.data["entity_name"] + self.detach_entity(skill_id, entity_name) + self.train() + + def handle_detach_intent(self, message): + skill_id = message.data["skill_id"] + intent_name = message.data["intent_name"] + self.detach_intent(skill_id, intent_name) + self.train() + + def handle_detach_skill(self, message): + skill_id = message.data["skill_id"] + self.detach_skill(skill_id) + self.train() + + # backwards compat bus handlers to keep around until ovos-core 0.1.0 + def _handle_adapt_vocab(self, message): """Register adapt-like vocabulary. This will handle both regex registration and registration of normal @@ -132,7 +195,6 @@ def handle_register_vocab(self, message): samples = [entity_value] entity_type = message.data.get('entity_type') - alias_of = message.data.get('alias_of') if is_r: # regex @@ -157,26 +219,10 @@ def handle_register_vocab(self, message): samples=samples, lang=self.lang) - def handle_detach_entity(self, message): - skill_id = message.data["skill_id"] - entity_name = message.data["entity_name"] - self.detach_entity(skill_id, entity_name) - self.train() - - def handle_detach_intent(self, message): - skill_id = message.data["skill_id"] - intent_name = message.data["intent_name"] - self.detach_intent(skill_id, intent_name) - self.train() - - def handle_detach_skill(self, message): - skill_id = message.data["skill_id"] - self.detach_skill(skill_id) - self.train() - # helpers to navigate registered intent data @property def manifest(self): + # ovos-core uses this property to expose the data via messagebus # TODO - munge/unmmunge skill_id in name ? return { "intent_names": [e.name for e in self.registered_intents From e22c4860e67dfaeba0c19d3a22e384ea7c639229 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 20 Sep 2023 16:33:25 +0100 Subject: [PATCH 4/7] more bus handlers --- ovos_plugin_manager/templates/pipeline.py | 57 +++++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/ovos_plugin_manager/templates/pipeline.py b/ovos_plugin_manager/templates/pipeline.py index fe372706..9f75cb2e 100644 --- a/ovos_plugin_manager/templates/pipeline.py +++ b/ovos_plugin_manager/templates/pipeline.py @@ -110,6 +110,55 @@ def register_intent_bus_handlers(self): self.bus.on('detach_skill', self._handle_detach_skill) # default bus handlers + def handle_register_keyword_intent(self, message): + skill_id = message.data.get("skill_id") or message.context.get("skill_id") + name = message.data["name"] + requires = message.data["requires"] + at_least_one = message.data.get("at_least_one", []) + optional = message.data.get("optional", []) + excludes = message.data.get("excludes", []) + self.register_keyword_intent(skill_id=skill_id, intent_name=name, required=requires, + at_least_one=at_least_one, optional=optional, + excluded=excludes) + + def handle_register_intent(self, message): + """Register intents + + message.data: + samples: list of natural language spoken utterances + name: the type/tag of an entity instance + + Args: + message (Message): message containing intent info + """ + skill_id = message.data.get("skill_id") or message.context.get("skill_id") + samples = message.data["samples"] + intent_type = message.data.get('name') + + self.register_intent(skill_id=skill_id, + intent_name=intent_type, + samples=samples, + lang=self.lang) + + def handle_register_regex_intent(self, message): + """Register intents + + message.data: + samples: regex patterns to match the intent + name: the type/tag of an entity instance + + Args: + message (Message): message containing intent info + """ + skill_id = message.data.get("skill_id") or message.context.get("skill_id") + samples = message.data["samples"] + intent_type = message.data.get('name') + + self.register_regex_intent(skill_id=skill_id, + intent_name=intent_type, + samples=samples, + lang=self.lang) + def handle_register_entity(self, message): """Register entities. @@ -150,13 +199,13 @@ def handle_register_regex_entity(self, message): def handle_detach_entity(self, message): skill_id = message.data["skill_id"] - entity_name = message.data["entity_name"] + entity_name = message.data["name"] self.detach_entity(skill_id, entity_name) self.train() def handle_detach_intent(self, message): skill_id = message.data["skill_id"] - intent_name = message.data["intent_name"] + intent_name = message.data["name"] self.detach_intent(skill_id, intent_name) self.train() @@ -297,12 +346,12 @@ def register_intent(self, skill_id, intent_name, samples, lang=None): intent = IntentDefinition(intent_name, lang=lang, samples=samples, skill_id=skill_id) self.registered_intents.append(intent) - def register_keyword_intent(self, skill_id, intent_name, keywords, + def register_keyword_intent(self, skill_id, intent_name, required, optional=None, at_least_one=None, excluded=None, lang=None): lang = lang or self.lang intent = KeywordIntentDefinition(intent_name, lang=lang, skill_id=skill_id, - requires=keywords, optional=optional, + requires=required, optional=optional, at_least_one=at_least_one, excluded=excluded) # NOTE - no merging here, we allow multiple variations of same intent with different rules to match self.registered_intents.append(intent) From ef5b8ed5f93b45812951286beda19d4420632b93 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 20 Sep 2023 16:41:24 +0100 Subject: [PATCH 5/7] compat bus handlers --- ovos_plugin_manager/templates/pipeline.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ovos_plugin_manager/templates/pipeline.py b/ovos_plugin_manager/templates/pipeline.py index 9f75cb2e..98b5ff5a 100644 --- a/ovos_plugin_manager/templates/pipeline.py +++ b/ovos_plugin_manager/templates/pipeline.py @@ -90,7 +90,6 @@ def __init__(self, bus, config=None): self.register_intent_bus_handlers() def register_intent_bus_handlers(self): - # WIP WIP WIP WIP self.bus.on('intent.service:detach_intent', self.handle_detach_intent) self.bus.on('intent.service:detach_entity', self.handle_detach_entity) self.bus.on('intent.service:detach_skill', self.handle_detach_skill) @@ -102,12 +101,12 @@ def register_intent_bus_handlers(self): # backwards compat handlers with adapt/padatious namespace # TODO - deprecate in ovos-core 0.1.0 - self.bus.on('padatious:register_intent', self._handle_padatious_intent) - self.bus.on('padatious:register_entity', self._handle_padatious_entity) + self.bus.on('padatious:register_intent', self.handle_register_intent) + self.bus.on('padatious:register_entity', self.handle_register_entity) self.bus.on('register_vocab', self._handle_adapt_vocab) - self.bus.on('register_intent', self._handle_adapt_intent) - self.bus.on('detach_intent', self._handle_detach_intent) - self.bus.on('detach_skill', self._handle_detach_skill) + self.bus.on('register_intent', self.handle_register_keyword_intent) + self.bus.on('detach_intent', self.handle_detach_intent) + self.bus.on('detach_skill', self.handle_detach_skill) # default bus handlers def handle_register_keyword_intent(self, message): @@ -205,7 +204,8 @@ def handle_detach_entity(self, message): def handle_detach_intent(self, message): skill_id = message.data["skill_id"] - intent_name = message.data["name"] + intent_name = message.data.get("name") or \ + message.data.get('intent_name') # adapt/padatious compat self.detach_intent(skill_id, intent_name) self.train() From 4404be692c1dc545efe938e6bb4a02fd7f3bd5d0 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Wed, 20 Sep 2023 18:01:08 +0100 Subject: [PATCH 6/7] valid langs property --- ovos_plugin_manager/templates/pipeline.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ovos_plugin_manager/templates/pipeline.py b/ovos_plugin_manager/templates/pipeline.py index 98b5ff5a..3b1840a7 100644 --- a/ovos_plugin_manager/templates/pipeline.py +++ b/ovos_plugin_manager/templates/pipeline.py @@ -35,6 +35,15 @@ def lang(self): self.config.get("lang") or \ "en-us" + @property + def valid_languages(self): + core_config = Configuration() + lang = core_config.get("lang", "en-us") + langs = core_config.get('secondary_langs') or [] + if lang not in langs: + langs.insert(0, lang) + return langs + @classproperty def matcher_id(self): raise NotImplementedError From bfc0ebc5faffae000839e2ec56283f8ac8b11b50 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Thu, 21 Sep 2023 22:49:33 +0100 Subject: [PATCH 7/7] bus api interface --- ovos_plugin_manager/templates/pipeline.py | 27 ++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ovos_plugin_manager/templates/pipeline.py b/ovos_plugin_manager/templates/pipeline.py index 3b1840a7..e994e2c4 100644 --- a/ovos_plugin_manager/templates/pipeline.py +++ b/ovos_plugin_manager/templates/pipeline.py @@ -105,8 +105,10 @@ def register_intent_bus_handlers(self): self.bus.on('intent.service.register_intent', self.handle_register_intent) self.bus.on('intent.service.register_keyword_intent', self.handle_register_keyword_intent) self.bus.on('intent.service.register_regex_intent', self.handle_register_regex_intent) + self.bus.on('intent.service:register_keyword', self.handle_register_keyword) self.bus.on('intent.service:register_entity', self.handle_register_entity) self.bus.on('intent.service:register_regex_entity', self.handle_register_regex_entity) + self.bus.on('intent.service:train', self.handle_train) # backwards compat handlers with adapt/padatious namespace # TODO - deprecate in ovos-core 0.1.0 @@ -116,8 +118,12 @@ def register_intent_bus_handlers(self): self.bus.on('register_intent', self.handle_register_keyword_intent) self.bus.on('detach_intent', self.handle_detach_intent) self.bus.on('detach_skill', self.handle_detach_skill) + self.bus.on('mycroft.skills.initialized', self.handle_train) # default bus handlers + def handle_train(self, message): + self.train() + def handle_register_keyword_intent(self, message): skill_id = message.data.get("skill_id") or message.context.get("skill_id") name = message.data["name"] @@ -125,9 +131,10 @@ def handle_register_keyword_intent(self, message): at_least_one = message.data.get("at_least_one", []) optional = message.data.get("optional", []) excludes = message.data.get("excludes", []) + lang = message.data.get("lang") or message.context.get("lang") or self.lang self.register_keyword_intent(skill_id=skill_id, intent_name=name, required=requires, at_least_one=at_least_one, optional=optional, - excluded=excludes) + excluded=excludes, lang=lang) def handle_register_intent(self, message): """Register intents @@ -167,6 +174,24 @@ def handle_register_regex_intent(self, message): samples=samples, lang=self.lang) + def handle_register_keyword(self, message): + """Register keywords. + + message.data: + samples: list of natural language words / utterance chunks + name: the type/tag of an entity instance + + Args: + message (Message): message containing vocab info + """ + skill_id = message.data.get("skill_id") or message.context.get("skill_id") + samples = message.data["samples"] + entity_type = message.data.get('name') + self.register_entity(skill_id=skill_id, + entity_name=entity_type, + samples=samples, + lang=self.lang) + def handle_register_entity(self, message): """Register entities.