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

fix:IntentHandlerMatch #14

Merged
merged 3 commits into from
Oct 31, 2024
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
31 changes: 15 additions & 16 deletions ovos_commonqa/opm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
from dataclasses import dataclass
from os.path import dirname
from threading import Event
from typing import Dict, Optional, List, Union
from typing import Dict, Optional, List, Union, Any, Tuple

from ovos_bus_client.client import MessageBusClient
from ovos_bus_client.message import Message
from ovos_bus_client.session import SessionManager
from ovos_config.config import Configuration
from ovos_plugin_manager.solvers import find_multiple_choice_solver_plugins
from ovos_plugin_manager.templates.pipeline import PipelineMatch, PipelineStageMatcher
from ovos_plugin_manager.templates.pipeline import ConfidenceMatcherPipeline, IntentHandlerMatch
from ovos_utils import flatten_list
from ovos_utils.fakebus import FakeBus
from ovos_utils.lang import standardize_lang_tag
Expand All @@ -31,15 +31,16 @@ class Query:
completed: Event = Event()
answered: bool = False
selected_skill: str = ""
callback_data: Optional[Dict[str, Any]] = None


class CommonQAService(PipelineStageMatcher, OVOSAbstractApplication):
class CommonQAService(ConfidenceMatcherPipeline, OVOSAbstractApplication):
def __init__(self, bus: Optional[Union[MessageBusClient, FakeBus]] = None,
config: Optional[Dict] = None):
OVOSAbstractApplication.__init__(
self, bus=bus, skill_id="common_query.openvoiceos",
resources_dir=f"{dirname(__file__)}")
PipelineStageMatcher.__init__(self, bus, config)
ConfidenceMatcherPipeline.__init__(self, bus, config)
JarbasAl marked this conversation as resolved.
Show resolved Hide resolved
self.active_queries: Dict[str, Query] = dict()

self.common_query_skills = []
Expand Down Expand Up @@ -72,7 +73,7 @@ def handle_skill_pong(self, message: Message):
self.common_query_skills.append(message.data["skill_id"])
LOG.debug("Detected CommonQuery skill: " + message.data["skill_id"])

def is_question_like(self, utterance: str, lang: str):
def is_question_like(self, utterance: str, lang: str) -> bool:
"""
Check if the input utterance looks like a question for CommonQuery
@param utterance: user input to evaluate
Expand All @@ -91,7 +92,7 @@ def is_question_like(self, utterance: str, lang: str):
# require a "question word"
return self.voc_match(utterance, "QuestionWord", lang)

def match(self, utterances: List[str], lang: str, message: Message) -> Optional[PipelineMatch]:
def match(self, utterances: List[str], lang: str, message: Message) -> Optional[IntentHandlerMatch]:
"""
Send common query request and select best response

Expand Down Expand Up @@ -119,16 +120,16 @@ def match(self, utterances: List[str], lang: str, message: Message) -> Optional[
if self.is_question_like(utterance, lang):
message.data["lang"] = lang # only used for speak method
message.data["utterance"] = utterance
answered, skill_id = self.handle_question(message)
answered, query = self.handle_question(message)
if answered:
match = PipelineMatch(handled=True,
match_data={},
skill_id=skill_id,
utterance=utterance)
match = IntentHandlerMatch(match_type='question:action',
match_data=query.callback_data,
skill_id=query.selected_skill,
utterance=utterance)
break
return match

def handle_question(self, message: Message):
def handle_question(self, message: Message) -> Tuple[bool, Query]:
"""
Send the phrase to CommonQuerySkills and prepare for handling replies.
"""
Expand Down Expand Up @@ -161,7 +162,6 @@ def handle_question(self, message: Message):
if not query.completed.is_set():
LOG.debug(f"Session Timeout gathering responses ({query.session_id})")
LOG.warning(f"Timed out getting responses for: {query.query}")
timeout = True
break

self._query_timeout(timeout_msg)
Expand All @@ -171,7 +171,7 @@ def handle_question(self, message: Message):
self.active_queries.pop(sess.session_id)
LOG.debug(f"answered={answered}|"
f"remaining active_queries={len(self.active_queries)}")
return answered, query.selected_skill
return answered, query

def handle_query_response(self, message: Message):
search_phrase = message.data['phrase']
Expand Down Expand Up @@ -280,8 +280,7 @@ def _query_timeout(self, message: Message):

LOG.info('Handling with: ' + str(best['skill_id']))
query.selected_skill = best["skill_id"]
response_data = {**best, "phrase": search_phrase}
self.bus.emit(message.reply('question:action', data=response_data))
query.callback_data = {**best, "phrase": search_phrase}
query.answered = True
else:
query.answered = False
Expand Down
38 changes: 2 additions & 36 deletions tests/test_common_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,45 +112,11 @@ def test_common_query_events(self):
# stop thinking animation
{'type': 'enclosure.mouth.reset',
'data': {},
'context': qq_ctxt},
# skill callback event
# the destination here is `skills` and skill_id context is
# CommonQuery since this event is not dictated by the selected skill
{'type': 'question:action',
'data': {'skill_id': 'wiki.test',
'answer': 'answer 1',
'conf': 0.74,
'handles_speech': True,
'phrase': 'what is the speed of light',
'callback_data': {'query': 'what is the speed of light',
'answer': 'answer 1'}},
'context': qq_ans_ctxt}, # destination: audio from this message forward
# skill was select, make it an active skill
{'context': qq_ans_ctxt,
'data': {'skill_id': 'wiki.test', 'timeout': 5.0},
'type': 'intent.service.skills.activate'},
# tell enclosure about active skill (speak method). This is the
# skill that provided the response and may follow-up with actions
# in a callback method
{'type': 'enclosure.active_skill',
'data': {'skill_id': 'wiki.test'},
'context': qq_ans_ctxt},
# execution of speak method. This is called from CommonQuery, but
# should report the skill which provided the response to match the
# enclosure active_skill and any follow-up actions in the callback
{'type': 'speak',
'data': {'utterance': 'answer 1',
'expect_response': False,
'meta': {'skill': 'wiki.test'},
'lang': 'en-US'},
'context': skill_ans_ctxt},
# handler complete event
{'type': 'mycroft.skill.handler.complete',
'data': {'handler': 'common_query'},
'context': skill_ans_ctxt},
'context': qq_ctxt}
]

for ctr, msg in enumerate(expected):
print(ctr, msg)
m: dict = self.bus.emitted_msgs[ctr]
if "session" in m.get("context", {}):
m["context"].pop("session") # simplify test comparisons
Expand Down
Loading