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

feat:pipeline plugin factory #570

Draft
wants to merge 10 commits into
base: dev
Choose a base branch
from
Draft
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
248 changes: 72 additions & 176 deletions ovos_core/intent_services/__init__.py

Large diffs are not rendered by default.

32 changes: 19 additions & 13 deletions ovos_core/intent_services/converse_service.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import time
from threading import Event
from typing import Optional, List
from typing import Optional, Dict, List, Union

from ovos_bus_client.client import MessageBusClient
from ovos_bus_client.message import Message
from ovos_bus_client.session import SessionManager, UtteranceState, Session
from ovos_bus_client.util import get_message_lang
from ovos_config.config import Configuration
from ovos_config.locale import setup_locale
from ovos_plugin_manager.templates.pipeline import PipelineMatch, PipelinePlugin
from ovos_plugin_manager.templates.pipeline import PipelineMatch, PipelineStageMatcher
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_utils.log import LOG, deprecated
from ovos_workshop.permissions import ConverseMode, ConverseActivationMode


class ConverseService(PipelinePlugin):
class ConverseService(PipelineStageMatcher):
"""Intent Service handling conversational skills."""

def __init__(self, bus):
self.bus = bus
def __init__(self, bus: Optional[Union[MessageBusClient, FakeBus]] = None,
config: Optional[Dict] = None):
config = config or Configuration().get("skills", {}).get("converse", {})
super().__init__(bus, config)
self._consecutive_activations = {}
self.bus.on('mycroft.speech.recognition.unknown', self.reset_converse)
self.bus.on('intent.service.skills.deactivate', self.handle_deactivate_skill_request)
Expand All @@ -27,7 +30,6 @@ def __init__(self, bus):
self.bus.on('intent.service.active_skills.get', self.handle_get_active_skills)
self.bus.on("skill.converse.get_response.enable", self.handle_get_response_enable)
self.bus.on("skill.converse.get_response.disable", self.handle_get_response_disable)
super().__init__(config=Configuration().get("skills", {}).get("converse") or {})

@property
def active_skills(self):
Expand Down Expand Up @@ -312,25 +314,25 @@ def converse(self, utterances: List[str], skill_id: str, lang: str, message: Mes
f'increasing "max_skill_runtime" in mycroft.conf might help alleviate this issue')
return False

def converse_with_skills(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]:
def match(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]:
"""
Attempt to converse with active skills for a given set of utterances.

Iterates through active skills to find one that can handle the utterance. Filters skills based on timeout and blacklist status.

Args:
utterances (List[str]): List of utterance strings to process
lang (str): 4-letter ISO language code for the utterances
message (Message): Message context for generating a reply

Returns:
PipelineMatch: Match details if a skill successfully handles the utterance, otherwise None
- handled (bool): Whether the utterance was fully handled
- match_data (dict): Additional match metadata
- skill_id (str): ID of the skill that handled the utterance
- updated_session (Session): Current session state after skill interaction
- utterance (str): The original utterance processed

Notes:
- Standardizes language tag
- Filters out blacklisted skills
Expand Down Expand Up @@ -414,6 +416,10 @@ def handle_get_active_skills(self, message: Message):
self.bus.emit(message.reply("intent.service.active_skills.reply",
{"skills": self.get_active_skills(message)}))

@deprecated("'converse_with_skills' has been renamed to 'match'", "2.0.0")
def converse_with_skills(self, utterances: List[str], lang: str, message: Message = None) -> Optional[PipelineMatch]:
return self.match(utterances, lang, message)

def shutdown(self):
self.bus.remove('mycroft.speech.recognition.unknown', self.reset_converse)
self.bus.remove('intent.service.skills.deactivate', self.handle_deactivate_skill_request)
Expand Down
58 changes: 41 additions & 17 deletions ovos_core/intent_services/fallback_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,43 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Intent service for Mycroft's fallback system."""
"""Intent service for OVOS's fallback system."""
import operator
import time
from collections import namedtuple
from typing import Optional, List
from typing import Optional, Dict, List, Union

from ovos_bus_client.client import MessageBusClient
from ovos_bus_client.message import Message
from ovos_bus_client.session import SessionManager
from ovos_config import Configuration
from ovos_plugin_manager.templates.pipeline import PipelineMatch, PipelinePlugin
from ovos_plugin_manager.templates.pipeline import PipelineMatch, PipelineStageConfidenceMatcher
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_utils.log import LOG, deprecated, log_deprecation
from ovos_workshop.permissions import FallbackMode

FallbackRange = namedtuple('FallbackRange', ['start', 'stop'])


class FallbackService(PipelinePlugin):
class FallbackService(PipelineStageConfidenceMatcher):
"""Intent Service handling fallback skills."""

def __init__(self, bus):
self.bus = bus
self.fallback_config = Configuration()["skills"].get("fallbacks", {})
def __init__(self, bus: Optional[Union[MessageBusClient, FakeBus]] = None,
config: Optional[Dict] = None):
config = config or Configuration().get("skills", {}).get("fallbacks", {})
super().__init__(bus, config)
self.registered_fallbacks = {} # skill_id: priority
self.bus.on("ovos.skills.fallback.register", self.handle_register_fallback)
self.bus.on("ovos.skills.fallback.deregister", self.handle_deregister_fallback)
super().__init__(self.fallback_config)

def handle_register_fallback(self, message: Message):
skill_id = message.data.get("skill_id")
priority = message.data.get("priority") or 101

# check if .conf is overriding the priority for this skill
priority_overrides = self.fallback_config.get("fallback_priorities", {})
priority_overrides = self.config.get("fallback_priorities", {})
if skill_id in priority_overrides:
new_priority = priority_overrides.get(skill_id)
LOG.info(f"forcing {skill_id} fallback priority from {priority} to {new_priority}")
Expand All @@ -71,12 +73,12 @@ def _fallback_allowed(self, skill_id: str) -> bool:
Returns:
permitted (bool): True if skill can fallback
"""
opmode = self.fallback_config.get("fallback_mode", FallbackMode.ACCEPT_ALL)
opmode = self.config.get("fallback_mode", FallbackMode.ACCEPT_ALL)
if opmode == FallbackMode.BLACKLIST and skill_id in \
self.fallback_config.get("fallback_blacklist", []):
self.config.get("fallback_blacklist", []):
return False
elif opmode == FallbackMode.WHITELIST and skill_id not in \
self.fallback_config.get("fallback_whitelist", []):
self.config.get("fallback_whitelist", []):
return False
return True

Expand Down Expand Up @@ -147,7 +149,7 @@ def attempt_fallback(self, utterances: List[str], skill_id: str, lang: str, mess
"lang": lang})
result = self.bus.wait_for_response(fb_msg,
f"ovos.skills.fallback.{skill_id}.response",
timeout=self.fallback_config.get("max_skill_runtime", 10))
timeout=self.config.get("max_skill_runtime", 10))
JarbasAl marked this conversation as resolved.
Show resolved Hide resolved
if result and 'error' in result.data:
error_msg = result.data['error']
LOG.error(f"{skill_id}: {error_msg}")
Expand Down Expand Up @@ -202,21 +204,43 @@ def _fallback_range(self, utterances: List[str], lang: str,
utterance=utterances[0])
return None

def high_prio(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]:
def match_high(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]:
"""Pre-padatious fallbacks."""
return self._fallback_range(utterances, lang, message,
FallbackRange(0, 5))

def medium_prio(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]:
def match_medium(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]:
"""General fallbacks."""
return self._fallback_range(utterances, lang, message,
FallbackRange(5, 90))

def low_prio(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]:
def match_low(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]:
"""Low prio fallbacks with general matching such as chat-bot."""
return self._fallback_range(utterances, lang, message,
FallbackRange(90, 101))

@deprecated("'low_prio' has been renamed to 'match_low'", "2.0.0")
def low_prio(self, utterances: List[str], lang: str, message: Message = None) -> Optional[PipelineMatch]:
return self.match_low(utterances, lang, message)

@deprecated("'medium_prio' has been renamed to 'match_medium'", "2.0.0")
def medium_prio(self, utterances: List[str], lang: str, message: Message = None) -> Optional[PipelineMatch]:
return self.match_medium(utterances, lang, message)

@deprecated("'high_prio' has been renamed to 'match_high'", "2.0.0")
def high_prio(self, utterances: List[str], lang: str, message: Message = None) -> Optional[PipelineMatch]:
return self.match_high(utterances, lang, message)

@property
def fallback_config(self) -> Dict:
log_deprecation("'self.fallback_config' is deprecated, access 'self.config' directly instead", "1.0.0")
return self.config

@fallback_config.setter
def fallback_config(self, val):
log_deprecation("'self.fallback_config' is deprecated, access 'self.config' directly instead", "1.0.0")
self.config = val

def shutdown(self):
self.bus.remove("ovos.skills.fallback.register", self.handle_register_fallback)
self.bus.remove("ovos.skills.fallback.deregister", self.handle_deregister_fallback)
Loading
Loading