-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Skill states #2901
Skill states #2901
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,8 @@ | |
| AdaptService, AdaptIntent, FallbackService, PadatiousService, IntentMatch | ||
| ) | ||
| from .intent_service_interface import open_intent_envelope | ||
| from mycroft.messagebus.message import Message | ||
| import json | ||
|
|
||
|
|
||
| def _get_message_lang(message): | ||
|
|
@@ -82,6 +84,7 @@ def __init__(self, bus): | |
| self.bus = bus | ||
|
|
||
| self.skill_names = {} | ||
| self.skill_categories = {} | ||
| config = Configuration.get() | ||
| self.adapt_service = AdaptService(config.get('context', {})) | ||
| try: | ||
|
|
@@ -104,12 +107,24 @@ def __init__(self, bus): | |
| # Converse method | ||
| self.bus.on('mycroft.speech.recognition.unknown', self.reset_converse) | ||
| self.bus.on('mycroft.skills.loaded', self.update_skill_name_dict) | ||
| self.bus.on('mycroft.skills.list', self.update_skill_list) | ||
|
|
||
|
|
||
| def add_active_skill_handler(message): | ||
| self.add_active_skill(message.data['skill_id']) | ||
| category = 'undefined' | ||
| if message.data.get("skill_cat", None) is not None: | ||
| category = message.data['skill_cat'] | ||
|
|
||
| self.add_active_skill(message.data['skill_id'], category) | ||
|
|
||
| def remove_active_skill_handler(message): | ||
| self.remove_active_skill(message.data['skill_id']) | ||
|
|
||
| self.bus.on('active_skill_request', add_active_skill_handler) | ||
| self.bus.on('deactivate_skill_request', remove_active_skill_handler) | ||
| self.active_skills = [] # [skill_id , timestamp] | ||
|
|
||
| # BUG fix this! | ||
| self.converse_timeout = 5 # minutes to prune active_skills | ||
|
|
||
| # Intents API | ||
|
|
@@ -138,6 +153,11 @@ def registered_intents(self): | |
| def update_skill_name_dict(self, message): | ||
| """Messagebus handler, updates dict of id to skill name conversions.""" | ||
| self.skill_names[message.data['id']] = message.data['name'] | ||
| self.bus.emit(Message('skillmanager.list')) | ||
|
|
||
| def update_skill_list(self, message): | ||
| for skill in message.data: | ||
| self.skill_categories[message.data[skill]['id']] = message.data[skill]['cat'] | ||
|
|
||
| def get_skill_name(self, skill_id): | ||
| """Get skill name from skill ID. | ||
|
|
@@ -167,6 +187,7 @@ def do_converse(self, utterances, skill_id, lang, message): | |
| lang (str): current language | ||
| message (Message): message containing interaction info. | ||
| """ | ||
| LOG.debug("do_converse()-utterances:%s, active_skills=%s" % (utterances, self.active_skills)) | ||
| converse_msg = (message.reply("skill.converse.request", { | ||
| "skill_id": skill_id, "utterances": utterances, "lang": lang})) | ||
| result = self.bus.wait_for_response(converse_msg, | ||
|
|
@@ -198,11 +219,11 @@ def remove_active_skill(self, skill_id): | |
| Arguments: | ||
| skill_id (str): skill to remove | ||
| """ | ||
| for skill in self.active_skills: | ||
| for skill in copy(self.active_skills): | ||
| if skill[0] == skill_id: | ||
| self.active_skills.remove(skill) | ||
|
|
||
| def add_active_skill(self, skill_id): | ||
| def add_active_skill(self, skill_id, category='undefined'): | ||
| """Add a skill or update the position of an active skill. | ||
|
|
||
| The skill is added to the front of the list, if it's already in the | ||
|
|
@@ -216,11 +237,13 @@ def add_active_skill(self, skill_id): | |
| if skill_id != '': | ||
| self.remove_active_skill(skill_id) | ||
| # add skill with timestamp to start of skill_list | ||
| self.active_skills.insert(0, [skill_id, time.time()]) | ||
| self.active_skills.insert(0, [skill_id, time.time(), category]) | ||
| else: | ||
| LOG.warning('Skill ID was empty, won\'t add to list of ' | ||
| 'active skills.') | ||
|
|
||
| LOG.debug("Exit add active skill_id:%s, active_skills=%s" % (skill_id, self.active_skills)) | ||
|
|
||
| def send_metrics(self, intent, context, stopwatch): | ||
| """Send timing metrics to the backend. | ||
|
|
||
|
|
@@ -272,6 +295,7 @@ def handle_utterance(self, message): | |
| Arguments: | ||
| message (Message): The messagebus data | ||
| """ | ||
| LOG.debug("Enter handle utterance: message.data:%s, active_skills:%s" % (message.data, self.active_skills)) | ||
| try: | ||
| lang = _get_message_lang(message) | ||
| set_active_lang(lang) | ||
|
|
@@ -297,9 +321,13 @@ def handle_utterance(self, message): | |
| match = match_func(combined, lang, message) | ||
| if match: | ||
| break | ||
|
|
||
| if match: | ||
| if match.skill_id: | ||
| self.add_active_skill(match.skill_id) | ||
| if self.skill_categories[match.skill_id] == 'undefined': | ||
| # note - new style will activate themselves | ||
| LOG.warning("adding old style skill to active_skills array") | ||
| self.add_active_skill(match.skill_id, self.skill_categories[match.skill_id]) | ||
| # If the service didn't report back the skill_id it | ||
| # takes on the responsibility of making the skill "active" | ||
|
|
||
|
|
@@ -333,12 +361,23 @@ def _converse(self, utterances, lang, message): | |
| if time.time() - skill[ | ||
| 1] <= self.converse_timeout * 60] | ||
|
|
||
|
|
||
| # first check for system levl skills | ||
| tmp_active_skills = copy(self.active_skills) | ||
| for skill in tmp_active_skills: | ||
| if skill[2] == 'system': | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the primary reason for having a category at the moment - to give Any Skill could then list themselves as a "system" Skill to gain the same priority access. It looks like the So if we have a way to hit the mostly recently active Skill first, for the Alarm Skill could we reactivate it ensuring it's at the top of the active list each time the beep is going to get played? |
||
| if self.do_converse(utterances, skill[0], lang, message): | ||
| # update timestamp, or there will be a timeout where | ||
| # intent stops conversing whether its being used or not | ||
| return IntentMatch('Converse', None, None, skill[0]) | ||
|
|
||
| # check if any skill wants to handle utterance | ||
| for skill in copy(self.active_skills): | ||
| for skill in tmp_active_skills: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this exclude the entries where
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought the above loop would cause us to exit the method on a match, are you saying if line 372 gets executed " return IntentMatch('Converse', None, None, skill[0])" we would still fall thru to the below loop?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could be reading the code wrong but I mean if none of the active_skills with the We would continue and run the whole list (including the skills with a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, I was just saying they would not run twice as the above loop would exit if a match occurred however you are correct that we could check if they were system level in the below loop to avoid wasting time checking them again, so yes i agree
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| if self.do_converse(utterances, skill[0], lang, message): | ||
| # update timestamp, or there will be a timeout where | ||
| # intent stops conversing whether its being used or not | ||
| return IntentMatch('Converse', None, None, skill[0]) | ||
|
|
||
| return None | ||
|
|
||
| def send_complete_intent_failure(self, message): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -126,6 +126,7 @@ def _get_last_modified_time(path): | |
| class SkillLoader: | ||
| def __init__(self, bus, skill_directory): | ||
| self.bus = bus | ||
| self.category = 'undefined' | ||
| self.skill_directory = skill_directory | ||
| self.skill_id = os.path.basename(skill_directory) | ||
| self.load_attempted = False | ||
|
|
@@ -305,6 +306,7 @@ def _create_skill_instance(self, skill_module): | |
| self.instance._register_decorated() | ||
| self.instance.register_resting_screen() | ||
| self.instance.initialize() | ||
| self.category = self.instance.category | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are the possible Skill categories? Are they verified anywhere or any Skill can provide whatever category they wish? If we're looking at "common_play" as the News Skill example shows, can we not infer that from the class that Skill inherits from? |
||
| except Exception as e: | ||
| # If an exception occurs, make sure to clean up the skill | ||
| self.instance.default_shutdown() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this meant to be in this PR?
If we want to add messages to each audio service then I think we need to namespace them appropriately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm tired from haggling. This system has been in this state for many years now. If someone had a better idea they should have fixed it by now. I can appreciate the attitude some of our community members and why they ultimately decide to fork. While there are many different ways these issues could be addressed I am not interested in altering this PR to work around any. Your ideas sound fine, please squash this PR get your ideas working and add me to the review list after you submit your new PR with the working code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Ken, as a Swede I don't generally interfere (neutrality and all that), I respect you and I'm sure this came off more aggressive than you intended but this comes of rather rude and I must speak out since I don't think Kris deserves this bite.
(This thread in particular is valid since it's a query about something that's not mentioned in the PR or comment at all.)
The general discussion about the approach is probably the more important thing, we need the open discussion to make better designs. That is one of the strengths of open source, sharing and building upon each other's ideas. Instead of biting back, answer his concerns and make your thoughts on the issue clear to the rest of us. I'm sure you've thought it through. I think the skill state may be an interesting feature, this is the initial commit and will need to be streamlined in someway to fit into the system but will likely be very convenient.
Will now go back to the Swedish way of "making a fist in my pocket", and be silent.
P.S. I think the general frustration in the community is when no response is given, not when response is received. D.S.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My message was simply if we have a better approach why have we waited so long to implement it? I have already put the effort into solving this problem, however, I am always open to alternative solutions and look forward to reviewing any contributions which solve the problem in a different manner. There is, in this case, about a 40 hour difference between a good idea and a working solution. I have other concerns regarding how we handle contributions from the community in general but that is a different topic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the active skills array we don't store intents, we store skill tuples (skill_id, timestamp, category). One thing I should explain is I have not verified if the stop intent handlers are still being called, I was just surprised they showed up in the intent results at all but maybe that is by design and they are not actually getting called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the active_skills are only used for converse calls. where active skills may short-circuit the intent service. The active skills list will not in any way block the normal intent handling through adapt, padatious and fallback services unless you changed the behavior (I don't see that here, but may be wrong).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if I disable an intent I would not expect to see its handler fire; converse or otherwise. Is that true or am I missing something?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will not fire if it's disabled, see my example below. In that case nothing is firing for the OtherAdapt and the "unknown" skill is triggered instead, as expected.
Edit: Converse is a separate system so that may fire disable_intent() will only disable single intent (adapt or padatious), which would not make a difference to the converse