diff --git a/ovos_core/intent_services/__init__.py b/ovos_core/intent_services/__init__.py index 94f23249003a..a2d16bc2793e 100644 --- a/ovos_core/intent_services/__init__.py +++ b/ovos_core/intent_services/__init__.py @@ -13,6 +13,7 @@ # limitations under the License. # from collections import namedtuple +from ovos_plugin_manager.templates.pipeline import PipelineComponentPlugin, PipelineMultiConfPlugin, IntentMatch from ovos_config.config import Configuration from ovos_config.locale import setup_locale @@ -25,8 +26,7 @@ from ovos_core.intent_services.fallback_service import FallbackService from ovos_core.intent_services.padacioso_service import PadaciosoService from ovos_core.transformers import MetadataTransformersService, UtteranceTransformersService -from ovos_utils.intents.intent_service_interface import open_intent_envelope -from ovos_utils.log import LOG +from ovos_utils.log import LOG, deprecated from ovos_utils.messagebus import get_message_lang from ovos_utils.metrics import Stopwatch @@ -35,16 +35,6 @@ except ImportError: from ovos_core.intent_services.padacioso_service import PadaciosoService as PadatiousService -# 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 IntentService: """Mycroft intent service. parses utterances using a variety of systems. @@ -60,8 +50,10 @@ def __init__(self, bus): # Dictionary for translating a skill id to a name self.skill_names = {} + self.pipeline_plugins = {} + # TODO - replace with plugins - self.adapt_service = AdaptService() + self.adapt_service = AdaptService(self.bus) if PadaciosoService is not PadatiousService: self.padatious_service = PadatiousService(bus, config['padatious']) else: @@ -71,11 +63,10 @@ def __init__(self, bus): self.fallback = FallbackService(bus) self.converse = ConverseService(bus) self.common_qa = CommonQAService(bus) + self.utterance_plugins = UtteranceTransformersService(bus, config=config) self.metadata_plugins = MetadataTransformersService(bus, config=config) - 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) @@ -129,6 +120,49 @@ def pipeline(self): "fallback_low" ]) + def load_pipeline_plugins(self): + plugins = {} # TODO + + for plug_name, plug in plugins.items(): + if issubclass(plug, PipelineMultiConfPlugin): + matchers = [plug.matcher_id, + plug.matcher_id + "_low", + plug.matcher_id + "_medium", + plug.matcher_id + "_high"] + else: + matchers = [plug.matcher_id] + + if any(m in self.pipeline for m in matchers): + try: + self.pipeline_plugins[plug_name] = plug(self.bus) + except Exception as e: + LOG.error(f"failed to load plugin {plug_name}:{e}") + continue + + @property + def pipeline_matchers(self): + matchers = [] + for m in self.pipeline: + matcher_id = m.replace("_high", "").replace("_medium", "").replace("_low", "") + if matcher_id not in self.pipeline_plugins: + LOG.error(f"{matcher_id} not installed, skipping pipeline stage!") + continue + + plugin = self.pipeline_plugins[matcher_id] + if m != matcher_id and not isinstance(plugin, PipelineMultiConfPlugin): + LOG.error("pipeline plugin should subclass PipelineMultiConfPlugin to support match levels") + continue + + if m.endswith("_high"): + matchers.append(plugin.match_high) + elif m.endswith("_medium"): + matchers.append(plugin.match_medium) + elif m.endswith("_low"): + matchers.append(plugin.match_low) + else: + matchers.append(plugin.match) + return matchers + @property def registered_intents(self): lang = get_message_lang() @@ -347,35 +381,25 @@ def send_complete_intent_failure(self, message): self.bus.emit(message.forward('mycroft.audio.play_sound', {"uri": sound})) self.bus.emit(message.forward('complete_intent_failure')) + @deprecated("handle_register_intent moved to AdaptService, overriding this method has no effect, " + "it has been disconnected from the bus event", "0.8.0") def handle_register_vocab(self, message): """Register adapt vocabulary. Args: message (Message): message containing vocab info """ - # TODO: 22.02 Remove backwards compatibility - if _is_old_style_keyword_message(message): - LOG.warning('Deprecated: Registering keywords with old message. ' - 'This will be removed in v22.02.') - _update_keyword_message(message) - - 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.adapt_service.register_vocabulary(entity_value, entity_type, - alias_of, regex_str, lang) - self.registered_vocab.append(message.data) + pass + @deprecated("handle_register_intent moved to AdaptService, overriding this method has no effect, " + "it has been disconnected from the bus event", "0.8.0") def handle_register_intent(self, message): """Register adapt intent. Args: message (Message): message containing intent info """ - intent = open_intent_envelope(message) - self.adapt_service.register_intent(intent) + pass def handle_detach_intent(self, message): """Remover adapt intent. @@ -547,29 +571,3 @@ def handle_entity_manifest(self, message): self.bus.emit(message.reply( "intent.service.padatious.entities.manifest", {"entities": self.padacioso_service.registered_entities})) - - -def _is_old_style_keyword_message(message): - """Simple check that the message is not using the updated format. - - TODO: Remove in v22.02 - - Args: - message (Message): Message object to check - - Returns: - (bool) True if this is an old messagem, else False - """ - return ('entity_value' not in message.data and 'start' in message.data) - - -def _update_keyword_message(message): - """Make old style keyword registration message compatible. - - Copies old keys in message data to new names. - - Args: - message (Message): Message to update - """ - message.data['entity_value'] = message.data['start'] - message.data['entity_type'] = message.data['end'] diff --git a/ovos_core/intent_services/adapt_service.py b/ovos_core/intent_services/adapt_service.py index 87ddefc4eea2..721bb83cec5a 100644 --- a/ovos_core/intent_services/adapt_service.py +++ b/ovos_core/intent_services/adapt_service.py @@ -18,11 +18,13 @@ from adapt.engine import IntentDeterminationEngine from ovos_config.config import Configuration -import ovos_core.intent_services from ovos_bus_client.session import IntentContextManager as ContextManager, \ SessionManager -from ovos_utils import flatten_list +from ovos_plugin_manager.templates.pipeline import PipelineComponentPlugin, IntentMatch +from ovos_utils import flatten_list, classproperty +from ovos_utils.intents.intent_service_interface import open_intent_envelope from ovos_utils.log import LOG +from ovos_utils.messagebus import get_message_lang, get_mycroft_bus def _entity_skill_id(skill_id): @@ -40,12 +42,14 @@ def _entity_skill_id(skill_id): return skill_id -class AdaptService: +class AdaptService(PipelineComponentPlugin): """Intent service wrapping the Adapt intent Parser.""" - def __init__(self, config=None): + def __init__(self, bus=None, config=None): + bus = bus or get_mycroft_bus() # backwards compat, bus was optional core_config = Configuration() - self.config = config or core_config.get("context", {}) + config = config or core_config.get("context", {}) + super().__init__(bus, config) self.lang = core_config.get("lang", "en-us") langs = core_config.get('secondary_langs') or [] if self.lang not in langs: @@ -53,9 +57,44 @@ def __init__(self, config=None): self.engines = {lang: IntentDeterminationEngine() for lang in langs} - self.lock = Lock() + @classproperty + def matcher_id(self): + return "adapt" + + # plugin api + def match(self, utterances, lang, message): + return self.match_intent(utterances, lang, message) + + def register_bus_events(self): + self.bus.on('register_vocab', self.handle_register_vocab) + self.bus.on('register_intent', self.handle_register_intent) + + # implementation + 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) + + 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) + @property def context_keywords(self): LOG.warning( @@ -180,7 +219,7 @@ def take_best(intent, utt): sess.context.update_context(ents) skill_id = best_intent['intent_type'].split(":")[0] - ret = ovos_core.intent_services.IntentMatch( + ret = IntentMatch( 'Adapt', best_intent['intent_type'], best_intent, skill_id, best_intent['utterance'] ) diff --git a/ovos_core/intent_services/commonqa_service.py b/ovos_core/intent_services/commonqa_service.py index a02acadb832a..d4f477288c04 100644 --- a/ovos_core/intent_services/commonqa_service.py +++ b/ovos_core/intent_services/commonqa_service.py @@ -1,12 +1,11 @@ import re -from threading import Lock, Event - import time from itertools import chain -from ovos_bus_client.message import Message, dig_for_message +from threading import Lock, Event -import ovos_core.intent_services -from ovos_utils import flatten_list +from ovos_bus_client.message import Message, dig_for_message +from ovos_plugin_manager.templates.pipeline import PipelineComponentPlugin, IntentMatch +from ovos_utils import flatten_list, classproperty from ovos_utils.enclosure.api import EnclosureAPI from ovos_utils.log import LOG from ovos_utils.messagebus import get_message_lang @@ -15,14 +14,14 @@ EXTENSION_TIME = 10 -class CommonQAService: +class CommonQAService(PipelineComponentPlugin): """Intent Service handling common query skills. All common query skills answer and the best answer is selected This is in contrast to triggering best intent directly. """ - def __init__(self, bus): - self.bus = bus + def __init__(self, bus, config=None): + super().__init__(bus, config) self.skill_id = "common_query.openvoiceos" # fake skill self.query_replies = {} # cache of received replies self.query_extensions = {} # maintains query timeout extensions @@ -32,9 +31,41 @@ def __init__(self, bus): self.answered = False self.enclosure = EnclosureAPI(self.bus, self.skill_id) self._vocabs = {} + + @classproperty + def matcher_id(self): + return "common_qa" + + # plugin api + def register_bus_events(self): self.bus.on('question:query.response', self.handle_query_response) self.bus.on('common_query.question', self.handle_question) + def match(self, utterances, lang, message): + """Send common query request and select best response + + Args: + utterances (list): List of tuples, + utterances and normalized version + lang (str): Language code + message: Message for session context + Returns: + IntentMatch or None + """ + # we call flatten in case someone is sending the old style list of tuples + utterances = flatten_list(utterances) + match = None + for utterance in utterances: + if self.is_question_like(utterance, lang): + message.data["lang"] = lang # only used for speak + message.data["utterance"] = utterance + answered = self.handle_question(message) + if answered: + match = IntentMatch('CommonQuery', None, {}, None, utterance) + break + return match + + # implementation def voc_match(self, utterance, voc_filename, lang, exact=False): """Determine if the given utterance contains the vocabulary provided. @@ -84,30 +115,6 @@ def is_question_like(self, utterance, lang): return False return True - def match(self, utterances, lang, message): - """Send common query request and select best response - - Args: - utterances (list): List of tuples, - utterances and normalized version - lang (str): Language code - message: Message for session context - Returns: - IntentMatch or None - """ - # we call flatten in case someone is sending the old style list of tuples - utterances = flatten_list(utterances) - match = None - for utterance in utterances: - if self.is_question_like(utterance, lang): - message.data["lang"] = lang # only used for speak - message.data["utterance"] = utterance - answered = self.handle_question(message) - if answered: - match = ovos_core.intent_services.IntentMatch('CommonQuery', None, {}, None, utterance) - break - return match - def handle_question(self, message): """ Send the phrase to the CommonQuerySkills and prepare for handling the replies. diff --git a/ovos_core/intent_services/converse_service.py b/ovos_core/intent_services/converse_service.py index 0eb26934ad3b..b8cd0ada709b 100644 --- a/ovos_core/intent_services/converse_service.py +++ b/ovos_core/intent_services/converse_service.py @@ -2,28 +2,30 @@ from ovos_bus_client.message import Message from ovos_config.config import Configuration -import ovos_core.intent_services -from ovos_utils import flatten_list +from ovos_utils import flatten_list, classproperty from ovos_utils.log import LOG from ovos_workshop.permissions import ConverseMode, ConverseActivationMode +from ovos_plugin_manager.templates.pipeline import PipelineComponentPlugin, IntentMatch -class ConverseService: +class ConverseService(PipelineComponentPlugin): """Intent Service handling conversational skills.""" - def __init__(self, bus): - self.bus = bus + def __init__(self, bus, config=None): + config = config or Configuration().get("skills", {}).get("converse") or {} + super().__init__(bus, config) self._consecutive_activations = {} self.active_skills = [] # [skill_id , timestamp] - @property - def config(self): - """ - Returns: - converse_config (dict): config for converse handling options - """ - return Configuration().get("skills", {}).get("converse") or {} + # plugin api + @classproperty + def matcher_id(self): + return "converse" + + def match(self, utterances: list, lang: str, message: Message): + return self.converse_with_skills(utterances, lang, message) + # implementation def get_active_skills(self): """Active skill ids ordered by converse priority this represents the order in which converse will be called @@ -261,5 +263,5 @@ def converse_with_skills(self, utterances, lang, message): # check if any skill wants to handle utterance for skill_id in self._collect_converse_skills(): if self.converse(utterances, skill_id, lang, message): - return ovos_core.intent_services.IntentMatch('Converse', None, None, skill_id, utterances[0]) + return IntentMatch('Converse', None, None, skill_id, utterances[0]) return None diff --git a/ovos_core/intent_services/fallback_service.py b/ovos_core/intent_services/fallback_service.py index baac1ded0615..e1844dde564e 100644 --- a/ovos_core/intent_services/fallback_service.py +++ b/ovos_core/intent_services/fallback_service.py @@ -14,29 +14,54 @@ # """Intent service for Mycroft's fallback system.""" import operator +import time from collections import namedtuple -import time from ovos_config import Configuration -import ovos_core.intent_services -from ovos_utils import flatten_list +from ovos_bus_client.message import Message +from ovos_plugin_manager.templates.pipeline import PipelineMultiConfPlugin, IntentMatch +from ovos_utils import flatten_list, classproperty from ovos_utils.log import LOG from ovos_workshop.skills.fallback import FallbackMode FallbackRange = namedtuple('FallbackRange', ['start', 'stop']) -class FallbackService: +class FallbackService(PipelineMultiConfPlugin): """Intent Service handling fallback skills.""" - def __init__(self, bus): - self.bus = bus - self.fallback_config = Configuration()["skills"].get("fallbacks", {}) + def __init__(self, bus, config=None): + config = config or Configuration()["skills"].get("fallbacks", {}) + super().__init__(bus, config) self.registered_fallbacks = {} # skill_id: priority + + # plugin api + @classproperty + def matcher_id(self): + return "fallback" + + def register_bus_events(self): self.bus.on("ovos.skills.fallback.register", self.handle_register_fallback) self.bus.on("ovos.skills.fallback.deregister", self.handle_deregister_fallback) + def match(self, utterances: list, lang: str, message: Message): + return self._fallback_range(utterances, lang, message, FallbackRange(0, 101)) + + def match_high(self, utterances: list, lang: str, message: Message): + return self.high_prio(utterances, lang, message) + + def match_medium(self, utterances: list, lang: str, message: Message): + return self.medium_prio(utterances, lang, message) + + def match_low(self, utterances: list, lang: str, message: Message): + return self.low_prio(utterances, lang, message) + + # implementation + @property + def fallback_config(self): + return self.config + def handle_register_fallback(self, message): skill_id = message.data.get("skill_id") priority = message.data.get("priority") or 101 @@ -165,7 +190,7 @@ def _fallback_range(self, utterances, lang, message, fb_range): for skill_id, prio in sorted_handlers: result = self.attempt_fallback(utterances, skill_id, lang, message) if result: - return ovos_core.intent_services.IntentMatch('Fallback', None, {}, None, utterances[0]) + return IntentMatch('Fallback', None, {}, None, utterances[0]) # old style deprecated fallback skill singleton class LOG.debug("checking for FallbackSkillsV1") @@ -178,7 +203,7 @@ def _fallback_range(self, utterances, lang, message, fb_range): response = self.bus.wait_for_response(msg, timeout=10) if response and response.data['handled']: - return ovos_core.intent_services.IntentMatch('Fallback', None, {}, None, utterances[0]) + return IntentMatch('Fallback', None, {}, None, utterances[0]) return None def high_prio(self, utterances, lang, message): diff --git a/ovos_core/intent_services/padacioso_service.py b/ovos_core/intent_services/padacioso_service.py index a34e2e0d4555..6f0d41d62409 100644 --- a/ovos_core/intent_services/padacioso_service.py +++ b/ovos_core/intent_services/padacioso_service.py @@ -1,16 +1,15 @@ """Intent service wrapping padacioso.""" -import concurrent.futures from functools import lru_cache from os.path import isfile from typing import List, Optional from ovos_config.config import Configuration -from ovos_utils import flatten_list -from ovos_utils.log import LOG from padacioso import IntentContainer as FallbackIntentContainer -import ovos_core.intent_services from ovos_bus_client.message import Message +from ovos_plugin_manager.templates.pipeline import PipelineMultiConfPlugin, IntentMatch +from ovos_utils import flatten_list, classproperty +from ovos_utils.log import LOG class PadaciosoIntent: @@ -43,12 +42,13 @@ def __repr__(self): return repr(self.__dict__) -class PadaciosoService: +class PadaciosoService(PipelineMultiConfPlugin): """Service class for padacioso intent matching.""" def __init__(self, bus, config): + super().__init__(bus, config) + self.padacioso_config = config - self.bus = bus core_config = Configuration() self.lang = core_config.get("lang", "en-us") @@ -66,14 +66,20 @@ def __init__(self, bus, config): self.padacioso_config.get("fuzz"), n_workers=self.workers) for lang in langs} + self.registered_intents = [] + self.registered_entities = [] + + # plugin api + @classproperty + def matcher_id(self): + return "padacioso" + + def register_bus_events(self): self.bus.on('padatious:register_intent', self.register_intent) self.bus.on('padatious:register_entity', self.register_entity) self.bus.on('detach_intent', self.handle_detach_intent) self.bus.on('detach_skill', self.handle_detach_skill) - self.registered_intents = [] - self.registered_entities = [] - def _match_level(self, utterances, limit, lang=None): """Match intent and make sure a certain level of confidence is reached. @@ -89,7 +95,7 @@ def _match_level(self, utterances, limit, lang=None): padacioso_intent = self.calc_intent(utterances, lang) if padacioso_intent is not None and padacioso_intent.conf > limit: skill_id = padacioso_intent.name.split(':')[0] - return ovos_core.intent_services.IntentMatch( + return IntentMatch( 'Padacioso', padacioso_intent.name, padacioso_intent.matches, skill_id, padacioso_intent.sent) @@ -120,6 +126,7 @@ def match_low(self, utterances, lang=None, message=None): """ return self._match_level(utterances, self.conf_low, lang) + # implementation def __detach_intent(self, intent_name): """ Remove an intent if it has been registered. diff --git a/ovos_core/intent_services/padatious_service.py b/ovos_core/intent_services/padatious_service.py index 3c0e83112147..a52d3d303ddd 100644 --- a/ovos_core/intent_services/padatious_service.py +++ b/ovos_core/intent_services/padatious_service.py @@ -13,7 +13,6 @@ # limitations under the License. # """Intent service wrapping padatious.""" -import concurrent.futures from functools import lru_cache from os import path from os.path import expanduser, isfile @@ -22,42 +21,24 @@ from typing import List, Optional import padatious -from padatious.match_data import MatchData as PadatiousIntent from ovos_config.config import Configuration from ovos_config.meta import get_xdg_base -from ovos_utils import flatten_list -from ovos_utils.log import LOG -from ovos_utils.xdg_utils import xdg_data_home +from padatious.match_data import MatchData as PadatiousIntent -import ovos_core.intent_services from ovos_bus_client.message import Message +from ovos_plugin_manager.templates.pipeline import PipelineMultiConfPlugin, IntentMatch +from ovos_utils import flatten_list, classproperty +from ovos_utils.log import LOG, deprecated +from ovos_utils.xdg_utils import xdg_data_home class PadatiousMatcher: """Matcher class to avoid redundancy in padatious intent matching.""" + @deprecated("PadatiousMatcher class is deprecated, use PadatiousService directly", "0.1.0") def __init__(self, service): self.service = service - def _match_level(self, utterances, limit, lang=None): - """Match intent and make sure a certain level of confidence is reached. - - Args: - utterances (list of tuples): Utterances to parse, originals paired - with optional normalized version. - limit (float): required confidence level. - """ - LOG.debug(f'Padatious Matching confidence > {limit}') - # call flatten in case someone is sending the old style list of tuples - utterances = flatten_list(utterances) - lang = lang or self.service.lang - padatious_intent = self.service.calc_intent(utterances, lang) - if padatious_intent is not None and padatious_intent.conf > limit: - skill_id = padatious_intent.name.split(':')[0] - return ovos_core.intent_services.IntentMatch( - 'Padatious', padatious_intent.name, - padatious_intent.matches, skill_id, padatious_intent.sent) - def match_high(self, utterances, lang=None, message=None): """Intent matcher for high confidence. @@ -65,7 +46,7 @@ def match_high(self, utterances, lang=None, message=None): utterances (list of tuples): Utterances to parse, originals paired with optional normalized version. """ - return self._match_level(utterances, self.service.conf_high, lang) + return self.service.match_high(utterances, lang, message) def match_medium(self, utterances, lang=None, message=None): """Intent matcher for medium confidence. @@ -74,7 +55,7 @@ def match_medium(self, utterances, lang=None, message=None): utterances (list of tuples): Utterances to parse, originals paired with optional normalized version. """ - return self._match_level(utterances, self.service.conf_med, lang) + return self.service.match_medium(utterances, lang, message) def match_low(self, utterances, lang=None, message=None): """Intent matcher for low confidence. @@ -83,13 +64,14 @@ def match_low(self, utterances, lang=None, message=None): utterances (list of tuples): Utterances to parse, originals paired with optional normalized version. """ - return self._match_level(utterances, self.service.conf_low, lang) + return self.service.match_low(utterances, lang, message) -class PadatiousService: +class PadatiousService(PipelineMultiConfPlugin): """Service class for padatious intent matching.""" def __init__(self, bus, config): + super().__init__(bus, config) self.padatious_config = config self.bus = bus @@ -110,12 +92,6 @@ def __init__(self, bus, config): lang: padatious.IntentContainer(path.join(expanduser(intent_cache), lang)) for lang in langs} - self.bus.on('padatious:register_intent', self.register_intent) - self.bus.on('padatious:register_entity', self.register_entity) - 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.train) - self.finished_training_event = Event() self.finished_initial_train = False @@ -125,6 +101,65 @@ def __init__(self, bus, config): self.registered_intents = [] self.registered_entities = [] + # plugin api + @classproperty + def matcher_id(self): + return "padatious" + + def register_bus_events(self): + self.bus.on('padatious:register_intent', self.register_intent) + self.bus.on('padatious:register_entity', self.register_entity) + 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.train) + + def _match_level(self, utterances, limit, lang=None): + """Match intent and make sure a certain level of confidence is reached. + + Args: + utterances (list of tuples): Utterances to parse, originals paired + with optional normalized version. + limit (float): required confidence level. + """ + LOG.debug(f'Padatious Matching confidence > {limit}') + # call flatten in case someone is sending the old style list of tuples + utterances = flatten_list(utterances) + lang = lang or self.lang + padatious_intent = self.calc_intent(utterances, lang) + if padatious_intent is not None and padatious_intent.conf > limit: + skill_id = padatious_intent.name.split(':')[0] + return IntentMatch( + 'Padatious', padatious_intent.name, + padatious_intent.matches, skill_id, padatious_intent.sent) + + def match_high(self, utterances, lang=None, message=None): + """Intent matcher for high confidence. + + Args: + utterances (list of tuples): Utterances to parse, originals paired + with optional normalized version. + """ + return self._match_level(utterances, self.conf_high, lang) + + def match_medium(self, utterances, lang=None, message=None): + """Intent matcher for medium confidence. + + Args: + utterances (list of tuples): Utterances to parse, originals paired + with optional normalized version. + """ + return self._match_level(utterances, self.conf_med, lang) + + def match_low(self, utterances, lang=None, message=None): + """Intent matcher for low confidence. + + Args: + utterances (list of tuples): Utterances to parse, originals paired + with optional normalized version. + """ + return self._match_level(utterances, self.conf_low, lang) + + # implementation def train(self, message=None): """Perform padatious training.