Skip to content

Commit

Permalink
feat!:pipeline plugin factory (#12)
Browse files Browse the repository at this point in the history
* feat:pipeline plugin factory

* feat:pipeline plugin factory

* feat:pipeline plugin factory

* feat:pipeline plugin factory
  • Loading branch information
JarbasAl authored Oct 16, 2024
1 parent 18c5911 commit 0859c2a
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 75 deletions.
179 changes: 105 additions & 74 deletions ovos_adapt/opm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand All @@ -61,68 +65,22 @@ 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
self.conf_high = self.config.get("conf_high") or 0.65
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.
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}))
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 0859c2a

Please sign in to comment.