Skip to content

Commit

Permalink
feat/neon_transformers
Browse files Browse the repository at this point in the history
  • Loading branch information
JarbasAl committed May 2, 2023
1 parent 6502d8a commit 89cddc6
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 88 deletions.
68 changes: 24 additions & 44 deletions ovos_core/intent_services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#


from collections import namedtuple

from ovos_config.config import Configuration
from ovos_config.locale import setup_locale

from ovos_bus_client.message import Message
from ovos_core.transformers import MetadataTransformersService, UtteranceTransformersService
from ovos_core.intent_services.adapt_service import AdaptService
from ovos_core.intent_services.commonqa_service import CommonQAService
from ovos_core.intent_services.converse_service import ConverseService
Expand All @@ -42,41 +40,6 @@
)


def _normalize_all_utterances(utterances):
"""Create normalized versions and pair them with the original utterance.
This will create a list of tuples with the original utterance as the
first item and if normalizing changes the utterance the normalized version
will be set as the second item in the tuple, if normalization doesn't
change anything the tuple will only have the "raw" original utterance.
Args:
utterances (list): list of utterances to normalize
Returns:
list of tuples, [(original utterance, normalized) ... ]
"""
try:
from lingua_franca.parse import normalize
# normalize() changes "it's a boy" to "it is a boy", etc.
norm_utterances = [normalize(u.lower(), remove_articles=False)
for u in utterances]
except:
norm_utterances = utterances

# Create pairs of original and normalized counterparts for each entry
# in the input list.
combined = []
for utt, norm in zip(utterances, norm_utterances):
if utt == norm:
combined.append((utt,))
else:
combined.append((utt, norm))

LOG.debug(f"Utterances: {combined}")
return combined


class IntentService:
"""Mycroft intent service. parses utterances using a variety of systems.
Expand All @@ -100,6 +63,8 @@ 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)
Expand Down Expand Up @@ -193,6 +158,20 @@ def reset_converse(self, message):
LOG.exception(f"Failed to set lingua_franca default lang to {lang}")
self.converse.converse_with_skills([], lang, message)

def _handle_transformers(self, message, lang):
"""
Pipe utterance through transformer plugins to get more metadata.
Utterances may be modified by any parser and context overwritten
"""
original = utterances = message.data.get('utterances', [])
message.context["lang"] = lang
utterances, message.context = self.utterance_plugins.transform(utterances, message.context)
if original != utterances:
message.data["utterances"] = utterances
LOG.debug(f"utterances transformed: {original} -> {utterances}")
message.context = self.metadata_plugins.transform(message.context)
return message

def handle_utterance(self, message):
"""Main entrypoint for handling user utterances with Mycroft skills
Expand Down Expand Up @@ -224,8 +203,10 @@ def handle_utterance(self, message):
except Exception as e:
LOG.exception(f"Failed to set lingua_franca default lang to {lang}")

# Get utterance utterance_plugins additional context
message = self._handle_transformers(message, lang)

utterances = message.data.get('utterances', [])
combined = _normalize_all_utterances(utterances)

stopwatch = Stopwatch()

Expand All @@ -242,11 +223,12 @@ def handle_utterance(self, message):
self.fallback.low_prio
]

# match
match = None
with stopwatch:
# Loop through the matching functions until a match is found.
for match_func in match_funcs:
match = match_func(combined, lang, message)
match = match_func(utterances, lang, message)
if match:
break
if match:
Expand Down Expand Up @@ -374,7 +356,6 @@ def handle_get_intent(self, message):
"""
utterance = message.data["utterance"]
lang = get_message_lang(message)
combined = _normalize_all_utterances([utterance])

# Create matchers
padatious_matcher = PadatiousMatcher(self.padatious_service)
Expand All @@ -394,7 +375,7 @@ def handle_get_intent(self, message):
]
# Loop through the matching functions until a match is found.
for match_func in match_funcs:
match = match_func(combined, lang, message)
match = match_func([utterance], lang, message)
if match:
if match.intent_type:
intent_data = match.intent_data
Expand Down Expand Up @@ -436,8 +417,7 @@ def handle_get_adapt(self, message):
"""
utterance = message.data["utterance"]
lang = get_message_lang(message)
combined = _normalize_all_utterances([utterance])
intent = self.adapt_service.match_intent(combined, lang)
intent = self.adapt_service.match_intent([utterance], lang)
intent_data = intent.intent_data if intent else None
self.bus.emit(message.reply("intent.service.adapt.reply",
{"intent": intent_data}))
Expand Down
34 changes: 18 additions & 16 deletions ovos_core/intent_services/adapt_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
# limitations under the License.
#
"""An intent parsing service using the Adapt parser."""
import time
from threading import Lock

import time
from adapt.context import ContextManagerFrame
from adapt.engine import IntentDeterminationEngine
from ovos_config.config import Configuration

import ovos_core.intent_services
from ovos_utils import flatten_list
from ovos_utils.log import LOG


Expand Down Expand Up @@ -211,6 +212,8 @@ def match_intent(self, utterances, lang=None, __=None):
Returns:
Intent structure, or None if no match was found.
"""
# we call flatten in case someone is sending the old style list of tuples
utterances = flatten_list(utterances)
lang = lang or self.lang
if lang not in self.engines:
return None
Expand All @@ -226,21 +229,20 @@ def take_best(intent, utt):
# TODO - Shouldn't Adapt do this?
best_intent['utterance'] = utt

for utt_tup in utterances:
for utt in utt_tup:
try:
intents = [i for i in self.engines[lang].determine_intent(
utt, 100,
include_tags=True,
context_manager=self.context_manager)]
if intents:
utt_best = max(
intents, key=lambda x: x.get('confidence', 0.0)
)
take_best(utt_best, utt_tup[0])

except Exception as err:
LOG.exception(err)
for utt in utterances:
try:
intents = [i for i in self.engines[lang].determine_intent(
utt, 100,
include_tags=True,
context_manager=self.context_manager)]
if intents:
utt_best = max(
intents, key=lambda x: x.get('confidence', 0.0)
)
take_best(utt_best, utt)

except Exception as err:
LOG.exception(err)

if best_intent:
self.update_context(best_intent)
Expand Down
23 changes: 14 additions & 9 deletions ovos_core/intent_services/commonqa_service.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import re
from threading import Lock, Event

import time
from itertools import chain
from threading import Lock, Event
from ovos_bus_client.message import Message, dig_for_message

import ovos_core.intent_services
from ovos_bus_client.message import Message, dig_for_message
from ovos_utils import flatten_list
from ovos_utils.enclosure.api import EnclosureAPI
from ovos_utils.log import LOG
from ovos_utils.messagebus import get_message_lang
Expand Down Expand Up @@ -93,14 +95,17 @@ def match(self, utterances, lang, message):
Returns:
IntentMatch or None
"""
# we call flatten in case someone is sending the old style list of tuples
utterances = flatten_list(utterances)
match = None
utterance = utterances[0][0]
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)
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)
break
return match

def handle_question(self, message):
Expand Down
7 changes: 4 additions & 3 deletions ovos_core/intent_services/converse_service.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import time

from ovos_bus_client.message import Message
from ovos_config.config import Configuration

import ovos_core.intent_services
from ovos_bus_client.message import Message
from ovos_utils import flatten_list
from ovos_utils.log import LOG
from ovos_workshop.permissions import ConverseMode, ConverseActivationMode

Expand Down Expand Up @@ -252,7 +252,8 @@ def converse_with_skills(self, utterances, lang, message):
Returns:
IntentMatch if handled otherwise None.
"""
utterances = [item for tup in utterances or [] for item in tup]
# we call flatten in case someone is sending the old style list of tuples
utterances = flatten_list(utterances)
# filter allowed skills
self._check_converse_timeout()
# check if any skill wants to handle utterance
Expand Down
16 changes: 9 additions & 7 deletions ovos_core/intent_services/fallback_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
# limitations under the License.
#
"""Intent service for Mycroft's fallback system."""
from collections import namedtuple
from ovos_config import Configuration
import operator
from collections import namedtuple

import time
from ovos_utils.log import LOG
from ovos_config import Configuration

import ovos_core.intent_services
from ovos_utils import flatten_list
from ovos_utils.log import LOG
from ovos_workshop.skills.fallback import FallbackMode

FallbackRange = namedtuple('FallbackRange', ['start', 'stop'])
Expand Down Expand Up @@ -81,7 +84,7 @@ def _collect_fallback_skills(self, message, fb_range=FallbackRange(0, 100)):

# filter skills outside the fallback_range
in_range = [s for s, p in self.registered_fallbacks.items()
if fb_range.start < p <= fb_range.stop]
if fb_range.start < p <= fb_range.stop]
skill_ids += [s for s in self.registered_fallbacks if s not in in_range]

def handle_ack(msg):
Expand Down Expand Up @@ -150,9 +153,8 @@ def _fallback_range(self, utterances, lang, message, fb_range):
Returns:
IntentMatch or None
"""
# NOTE - utterances here is a list of tuple of [(ut, norm)]
# they have been munged along the way... TODO undo the munging at the source
utterances = [u[0] for u in utterances]
# we call flatten in case someone is sending the old style list of tuples
utterances = flatten_list(utterances)
message.data["utterances"] = utterances # all transcripts
message.data["lang"] = lang

Expand Down
20 changes: 11 additions & 9 deletions ovos_core/intent_services/padatious_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
from os.path import expanduser, isfile
from subprocess import call
from threading import Event
from time import time as get_time, sleep

from ovos_bus_client.message import Message
from ovos_config.config import Configuration
from padacioso import IntentContainer as FallbackIntentContainer
from time import time as get_time, sleep

import ovos_core.intent_services
from ovos_bus_client.message import Message
from ovos_utils import flatten_list
from ovos_utils.log import LOG

try:
Expand Down Expand Up @@ -81,18 +82,19 @@ def _match_level(self, utterances, limit, lang=None):
with optional normalized version.
limit (float): required confidence level.
"""
# we call flatten in case someone is sending the old style list of tuples
utterances = flatten_list(utterances)
if not self.has_result:
lang = lang or self.service.lang
padatious_intent = None
LOG.debug(f'Padatious Matching confidence > {limit}')
for utt in utterances:
for variant in utt:
intent = self.service.calc_intent(variant, lang)
if intent:
best = padatious_intent.conf if padatious_intent else 0.0
if best < intent.conf:
padatious_intent = intent
padatious_intent.matches['utterance'] = utt[0]
intent = self.service.calc_intent(utt, lang)
if intent:
best = padatious_intent.conf if padatious_intent else 0.0
if best < intent.conf:
padatious_intent = intent
padatious_intent.matches['utterance'] = utt[0]
if padatious_intent:
skill_id = padatious_intent.name.split(':')[0]
self.ret = ovos_core.intent_services.IntentMatch(
Expand Down
Loading

0 comments on commit 89cddc6

Please sign in to comment.