Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Skill base classes #480

Merged
merged 3 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions neon_utils/skills/common_play_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
from enum import Enum, IntEnum
from abc import ABC, abstractmethod
from ovos_bus_client import Message
from ovos_utils.log import log_deprecation

from neon_utils.skills.neon_skill import NeonSkill

from ovos_utils.skills.audioservice import AudioServiceInterface as AudioService
Expand Down Expand Up @@ -85,6 +87,9 @@ class CommonPlaySkill(NeonSkill, ABC):
is needed.
"""
def __init__(self, *args, **kwargs):
log_deprecation("This base class is deprecated. Implement "
"`ovos_workshop.skills.common_play."
"OVOSCommonPlaybackSkill`", "2.0.0")
NeonSkill.__init__(self, *args, **kwargs)
self.audioservice = None
self.play_service_string = None
Expand Down
84 changes: 31 additions & 53 deletions neon_utils/skills/common_query_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,34 +40,31 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from enum import IntEnum
from abc import ABC, abstractmethod
from neon_utils.skills.neon_skill import NeonSkill


class CQSMatchLevel(IntEnum):
EXACT = 1 # Skill could find a specific answer for the question
CATEGORY = 2 # Skill could find an answer from a category in the query
GENERAL = 3 # The query could be processed as a general query

from abc import abstractmethod
from os.path import dirname

# Copy of CQSMatchLevel to use if the skill returns visual media
CQSVisualMatchLevel = IntEnum('CQSVisualMatchLevel',
[e.name for e in CQSMatchLevel])
from ovos_workshop.skills.common_query_skill import CQSMatchLevel, CQSVisualMatchLevel
from ovos_workshop.skills.common_query_skill import CommonQuerySkill as _CQS
from ovos_utils.file_utils import resolve_resource_file
from ovos_utils.log import log_deprecation
from neon_utils.skills.neon_skill import NeonSkill


def is_CQSVisualMatchLevel(match_level):
log_deprecation("This method is deprecated", "2.0.0")
return isinstance(match_level, type(CQSVisualMatchLevel.EXACT))


VISUAL_DEVICES = ['mycroft_mark_2']


def handles_visuals(platform):
log_deprecation("This method is deprecated", "2.0.0")
return platform in VISUAL_DEVICES


class CommonQuerySkill(NeonSkill, ABC):
# TODO: Consider deprecation and implementing ovos_workshop directly
class CommonQuerySkill(NeonSkill, _CQS):
"""Question answering skills should be based on this class.

The skill author needs to implement `CQS_match_query_phrase` returning an
Expand All @@ -78,47 +75,28 @@ class CommonQuerySkill(NeonSkill, ABC):
answers from several skills presenting the best one available.
"""
def __init__(self, *args, **kwargs):
# these should probably be configurable
self.level_confidence = {
CQSMatchLevel.EXACT: 0.9,
CQSMatchLevel.CATEGORY: 0.6,
CQSMatchLevel.GENERAL: 0.5
}
NeonSkill.__init__(self, *args, **kwargs)

def bind(self, bus):
"""Overrides the default bind method of MycroftSkill.

This registers messagebus handlers for the skill during startup
but is nothing the skill author needs to consider.
"""
if bus:
super().bind(bus)
self.add_event('question:query', self.__handle_question_query)
self.add_event('question:action', self.__handle_query_action)

def __handle_question_query(self, message):
search_phrase = message.data["phrase"]

# First, notify the requestor that we are attempting to handle
# (this extends a timeout while this skill looks for a match)
self.bus.emit(message.response({"phrase": search_phrase,
"skill_id": self.skill_id,
"searching": True}))

# Now invoke the CQS handler to let the skill perform its search
result = self.CQS_match_query_phrase(search_phrase, message)

if result:
match = result[0]
level = result[1]
answer = result[2]
callback = result[3] if len(result) > 3 else None
confidence = self.__calc_confidence(match, search_phrase, level)
self.bus.emit(message.response({"phrase": search_phrase,
"skill_id": self.skill_id,
"answer": answer,
"callback_data": callback,
"conf": confidence}))
else:
# Signal we are done (can't handle it)
self.bus.emit(message.response({"phrase": search_phrase,
"skill_id": self.skill_id,
"searching": False}))
noise_words_filepath = f"text/{self.lang}/noise_words.list"
default_res = f"{dirname(dirname(__file__))}/res/text/{self.lang}" \
f"/noise_words.list"
noise_words_filename = \
resolve_resource_file(noise_words_filepath,
config=self.config_core) or \
resolve_resource_file(default_res, config=self.config_core)

self._translated_noise_words = {}
if noise_words_filename:
with open(noise_words_filename) as f:
translated_noise_words = f.read().strip()
self._translated_noise_words[self.lang] = \
translated_noise_words.split()

def __calc_confidence(self, match, phrase, level):
# Assume the more of the words that get consumed, the better the match
Expand Down
6 changes: 3 additions & 3 deletions neon_utils/skills/neon_fallback_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from neon_utils.skills.neon_skill import NeonSkill
from ovos_workshop.skills.ovos import OVOSSkill
from ovos_utils.intents import IntentLayers
from ovos_workshop.decorators.layers import IntentLayers
from ovos_workshop.skills.fallback import FallbackSkillV1 as FallbackSkill
from ovos_workshop.skills.fallback import FallbackSkillV1


class NeonFallbackSkill(FallbackSkill, NeonSkill, OVOSSkill):
# TODO: Consider deprecation and implementing ovos_workshop directly
class NeonFallbackSkill(NeonSkill, FallbackSkillV1):
"""
Class that extends the NeonSkill and FallbackSkill classes to provide
NeonSkill functionality to any Fallback skill subclassing this class.
Expand Down
20 changes: 16 additions & 4 deletions tests/neon_skill_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,20 @@
from os.path import join
from threading import Thread
from time import sleep, time

from ovos_bus_client import Message
from ovos_utils.messagebus import FakeBus
from mock import Mock

from neon_utils.skills import NeonSkill, CommonMessageSkill, CommonPlaySkill, CommonQuerySkill, NeonFallbackSkill

sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
from neon_utils.cache_utils import LRUCache
from neon_utils.signal_utils import check_for_signal

sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from skills import *
from skills import (PatchedMycroftSkill, TestCMS, TestCPS, TestCQS, TestFBS,
TestPatchedSkill, TestInstructorSkill, TestChatSkill,
TestNeonSkill, TestKioskSkill, TestMycroftFallbackSkill)

MycroftSkill = PatchedMycroftSkill
ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
Expand Down Expand Up @@ -337,8 +341,8 @@ def handle_register(message):
self.assertEqual(resp.msg_type, f'{test_message.msg_type}.response')
self.assertEqual(resp.data,
{'response': test_message.data['test_response']})
self.assertEqual(resp.context, {'test': True,
'skill_id': skill.skill_id})
self.assertTrue(resp.context['test'])
self.assertEqual(resp.context['skill_id'], skill.skill_id)


class PatchedMycroftSkillTests(unittest.TestCase):
Expand Down Expand Up @@ -1074,6 +1078,10 @@ def setUpClass(cls) -> None:
cls.skill.load_data_files()
cls.skill.initialize()

cls.skill.config_core.setdefault('language', dict())
cls.skill.config_core['language']['detection_module'] = "libretranslate_detection_plug"
cls.skill.config_core['language']['translation_module'] = "libretranslate_plug"

@classmethod
def tearDownClass(cls) -> None:
if os.path.isdir(cls.config_dir):
Expand All @@ -1088,6 +1096,10 @@ def test_00_skill_init(self):
self.assertIsInstance(self.skill.neon_core, bool)
self.assertIsInstance(self.skill.skill_mode, str)
self.assertIsInstance(self.skill.extension_time, int)
self.assertEqual(self.skill.config_core['language']['detection_module'],
"libretranslate_detection_plug")
self.assertEqual(self.skill.config_core['language']['translation_module'],
"libretranslate_plug")
self.assertIsNotNone(self.skill.lang_detector)
self.assertIsNotNone(self.skill.translator)

Expand Down
6 changes: 3 additions & 3 deletions tests/signal_util_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ def on_create(message):
create_signal("test_signal")
self.assertIsInstance(msg, Message)
self.assertEqual(msg.data, {'signal_name': 'test_signal'})
self.assertEqual(msg.context,
{'origin_module': 'tests.signal_util_tests',
'origin_line': 130})
self.assertEqual(msg.context['origin_module'],
'tests.signal_util_tests')
self.assertEqual(msg.context['origin_line'], 130)

def test_signal_utils_manager_available(self):
TestSignalManager(self.test_bus)
Expand Down