1313# limitations under the License.
1414#
1515"""Mycroft's intent service, providing intent parsing since forever!"""
16- from copy import copy
1716import time
17+ from threading import Event
1818
1919from mycroft .configuration import Configuration , setup_locale
20- from mycroft .util .log import LOG
21- from mycroft .util .parse import normalize
20+ from mycroft .messagebus .message import Message
2221from mycroft .metrics import report_timing , Stopwatch
22+ from mycroft .skills .intent_service_interface import open_intent_envelope
2323from mycroft .skills .intent_services import (
24- AdaptService , AdaptIntent ,
25- FallbackService ,
24+ AdaptService , FallbackService ,
2625 PadatiousService , PadatiousMatcher ,
27- IntentMatch
26+ ConverseService , IntentMatch
2827)
29- from mycroft .skills .intent_service_interface import open_intent_envelope
3028from mycroft .skills .permissions import ConverseMode , ConverseActivationMode
31- from mycroft .messagebus .message import Message
29+ from mycroft .util .log import LOG
30+ from mycroft .util .parse import normalize
3231
3332
3433def _get_message_lang (message ):
@@ -81,20 +80,20 @@ class IntentService:
8180 The intent service also provides the internal API for registering and
8281 querying the intent service.
8382 """
83+
8484 def __init__ (self , bus ):
8585 self .bus = bus
8686 config = Configuration .get ()
8787
8888 # Dictionary for translating a skill id to a name
8989 self .skill_names = {}
90-
9190 self .adapt_service = AdaptService (config .get ('context' , {}))
9291 try :
9392 self .padatious_service = PadatiousService (bus , config ['padatious' ])
9493 except Exception as err :
95- LOG .exception ('Failed to create padatious handlers '
96- '({})' .format (repr (err )))
94+ LOG .exception (f'Failed to create padatious handlers ({ err } )' )
9795 self .fallback = FallbackService (bus )
96+ self .converse = ConverseService (bus )
9897
9998 self .bus .on ('register_vocab' , self .handle_register_vocab )
10099 self .bus .on ('register_intent' , self .handle_register_intent )
@@ -107,7 +106,6 @@ def __init__(self, bus):
107106 self .bus .on ('clear_context' , self .handle_clear_context )
108107
109108 # Converse method
110- self .converse_config = config ["skills" ].get ("converse" ) or {}
111109 self .bus .on ('mycroft.speech.recognition.unknown' , self .reset_converse )
112110 self .bus .on ('mycroft.skills.loaded' , self .update_skill_name_dict )
113111
@@ -118,9 +116,6 @@ def __init__(self, bus):
118116 # TODO backwards compat, deprecate
119117 self .bus .on ('active_skill_request' , self .handle_activate_skill_request )
120118
121- self .active_skills = [] # [skill_id , timestamp]
122- self ._consecutive_activations = {}
123-
124119 # Intents API
125120 self .registered_vocab = []
126121 self .bus .on ('intent.service.intent.get' , self .handle_get_intent )
@@ -160,67 +155,18 @@ def get_skill_name(self, skill_id):
160155 return self .skill_names .get (skill_id , skill_id )
161156
162157 # converse handling
163- def handle_activate_skill_request (self , message ):
164-
165- skill_id = message .data ['skill_id' ]
166-
167- # cross activation control if skills can activate each other
168- if not self .converse_config .get ("cross_activation" ):
169- # TODO imperfect solution - only a skill can activate itself
170- # someone can forge this message and emit it raw, but in OpenVoiceOS all
171- # skill messages should have skill_id in context, so let's make sure
172- # this doesnt happen accidentally at very least
173- source_skill = message .context .get ("skill_id" ) or skill_id
174- if skill_id != source_skill :
175- # different skill is trying to activate this skill
176- return
177-
178- # mode of activation dictates under what conditions a skill is
179- # allowed to activate itself
180- acmode = self .converse_config .get ("converse_activation" ) or \
181- ConverseActivationMode .ACCEPT_ALL
182-
183- if acmode == ConverseActivationMode .PRIORITY :
184- prio = self .converse_config .get ("converse_priorities" ) or {}
185- # only allowed to activate if no skill with higher priority is
186- # active, currently there is no api for skills to
187- # define their default priority, this is a user/developer setting
188- priority = prio .get (skill_id , 50 )
189- if any (p > priority for p in
190- [prio .get (s [0 ], 50 ) for s in self .active_skills ]):
191- return
192-
193- if acmode == ConverseActivationMode .BLACKLIST :
194- if skill_id in self .converse_config .get ("converse_blacklist" , []):
195- return
196-
197- if acmode == ConverseActivationMode .WHITELIST :
198- if skill_id not in self .converse_config .get ("converse_whitelist" , []):
199- return
200-
201- # limit of consecutive activations
202- default_max = self .converse_config .get ("max_activations" , - 1 )
203- # per skill override limit of consecutive activations
204- skill_max = self .converse_config .get ("skill_activations" , {}).get (skill_id )
205- max_activations = skill_max or default_max
206- if skill_id not in self ._consecutive_activations :
207- self ._consecutive_activations [skill_id ] = 0
208- if max_activations < 0 :
209- pass # no limit (mycroft-core default)
210- elif max_activations == 0 :
211- return # skill activation disabled
212- elif self ._consecutive_activations .get (skill_id , 0 ) > max_activations :
213- return # skill exceeded authorized consecutive number of
214- # activations
215-
216- # activate skill
217- self .add_active_skill (skill_id )
218- self ._consecutive_activations [skill_id ] += 1
219-
220- # converse handling
158+ @property
159+ def active_skills (self ):
160+ return self .converse .active_skills # [skill_id , timestamp]
221161
222162 def handle_activate_skill_request (self , message ):
223- self .add_active_skill (message .data ['skill_id' ])
163+ # TODO imperfect solution - only a skill can activate itself
164+ # someone can forge this message and emit it raw, but in OpenVoiceOS all
165+ # skill messages should have skill_id in context, so let's make sure
166+ # this doesnt happen accidentally at very least
167+ skill_id = message .data ['skill_id' ]
168+ source_skill = message .context .get ("skill_id" )
169+ self .converse .activate_skill (skill_id , source_skill )
224170
225171 def handle_deactivate_skill_request (self , message ):
226172 # TODO imperfect solution - only a skill can deactivate itself
@@ -229,15 +175,13 @@ def handle_deactivate_skill_request(self, message):
229175 # this doesnt happen accidentally
230176 skill_id = message .data ['skill_id' ]
231177 source_skill = message .context .get ("skill_id" ) or skill_id
232- if skill_id == source_skill :
233- self .remove_active_skill (message .data ['skill_id' ])
178+ self .converse .deactivate_skill (skill_id , source_skill )
234179
235180 def reset_converse (self , message ):
236181 """Let skills know there was a problem with speech recognition"""
237182 lang = _get_message_lang (message )
238183 setup_locale (lang ) # restore default lang
239- for skill in copy (self .active_skills ):
240- self .do_converse (None , skill [0 ], lang , message )
184+ self .converse .converse_with_skills (None , lang , message )
241185
242186 def do_converse (self , utterances , skill_id , lang , message ):
243187 """Call skill and ask if they want to process the utterance.
@@ -249,56 +193,29 @@ def do_converse(self, utterances, skill_id, lang, message):
249193 lang (str): current language
250194 message (Message): message containing interaction info.
251195 """
252- opmode = self .converse_config .get ("converse_mode" ,
253- ConverseMode .ACCEPT_ALL )
254-
255- if opmode == ConverseMode .BLACKLIST and skill_id in \
256- self .converse_config .get ("converse_blacklist" , []):
257- return False
258-
259- elif opmode == ConverseMode .WHITELIST and skill_id not in \
260- self .converse_config .get ("converse_whitelist" , []):
261- return False
262-
263- converse_msg = (message .reply ("skill.converse.request" , {
264- "skill_id" : skill_id , "utterances" : utterances , "lang" : lang }))
265- result = self .bus .wait_for_response (converse_msg ,
266- 'skill.converse.response' )
267- if result and 'error' in result .data :
268- self .handle_converse_error (result )
269- ret = False
270- elif result is not None :
271- ret = result .data .get ('result' , False )
272- else :
273- ret = False
274- return ret
196+ # NOTE: can not delete method for backwards compat with upstream
197+ LOG .warning ("self.do_converse has been deprecated!\n "
198+ "use self.converse.converse instead" )
199+ return self .converse .converse (utterances , skill_id , lang , message )
275200
276201 def handle_converse_error (self , message ):
277202 """Handle error in converse system.
278-
279203 Args:
280204 message (Message): info about the error.
281205 """
282- skill_id = message .data ["skill_id" ]
283- error_msg = message .data ['error' ]
284- LOG .error ("{}: {}" .format (skill_id , error_msg ))
285- if message .data ["error" ] == "skill id does not exist" :
286- self .remove_active_skill (skill_id )
206+ # NOTE: can not delete method for backwards compat with upstream
207+ LOG .warning ("handle_converse_error has been deprecated!" )
287208
288209 def remove_active_skill (self , skill_id ):
289210 """Remove a skill from being targetable by converse.
290211
291212 Args:
292213 skill_id (str): skill to remove
293214 """
294- for skill in self .active_skills :
295- if skill [0 ] == skill_id :
296- self .active_skills .remove (skill )
297- self .bus .emit (
298- Message ("intent.service.skills.deactivated" ,
299- {"skill_id" : skill_id }))
300- if skill_id in self ._consecutive_activations :
301- self ._consecutive_activations [skill_id ] = 0
215+ # NOTE: can not delete method for backwards compat with upstream
216+ LOG .warning ("self.remove_active_skill has been deprecated!\n "
217+ "use self.converse.deactivate_skill instead" )
218+ self .converse .deactivate_skill (skill_id )
302219
303220 def add_active_skill (self , skill_id ):
304221 """Add a skill or update the position of an active skill.
@@ -309,21 +226,10 @@ def add_active_skill(self, skill_id):
309226 Args:
310227 skill_id (str): identifier of skill to be added.
311228 """
312- # search the list for an existing entry that already contains it
313- # and remove that reference
314- if skill_id != '' :
315- # do not call remove method to not send deactivate bus event!
316- for skill in self .active_skills :
317- if skill [0 ] == skill_id :
318- self .active_skills .remove (skill )
319- # add skill with timestamp to start of skill_list
320- self .active_skills .insert (0 , [skill_id , time .time ()])
321- self .bus .emit (
322- Message ("intent.service.skills.activated" ,
323- {"skill_id" : skill_id }))
324- else :
325- LOG .warning ('Skill ID was empty, won\' t add to list of '
326- 'active skills.' )
229+ # NOTE: can not delete method for backwards compat with upstream
230+ LOG .warning ("self.add_active_skill has been deprecated!\n "
231+ "use self.converse.activate_skill instead" )
232+ self .converse .activate_skill (skill_id )
327233
328234 def send_metrics (self , intent , context , stopwatch ):
329235 """Send timing metrics to the backend.
@@ -391,7 +297,7 @@ def handle_utterance(self, message):
391297 # List of functions to use to match the utterance with intent.
392298 # These are listed in priority order.
393299 match_funcs = [
394- self ._converse , padatious_matcher .match_high ,
300+ self .converse . converse_with_skills , padatious_matcher .match_high ,
395301 self .adapt_service .match_intent , self .fallback .high_prio ,
396302 padatious_matcher .match_medium , self .fallback .medium_prio ,
397303 padatious_matcher .match_low , self .fallback .low_prio
@@ -406,7 +312,7 @@ def handle_utterance(self, message):
406312 break
407313 if match :
408314 if match .skill_id :
409- self .add_active_skill (match .skill_id )
315+ self .converse . activate_skill (match .skill_id )
410316 # If the service didn't report back the skill_id it
411317 # takes on the responsibility of making the skill "active"
412318
@@ -427,35 +333,6 @@ def handle_utterance(self, message):
427333 except Exception as err :
428334 LOG .exception (err )
429335
430- def _converse (self , utterances , lang , message ):
431- """Give active skills a chance at the utterance
432-
433- Args:
434- utterances (list): list of utterances
435- lang (string): 4 letter ISO language code
436- message (Message): message to use to generate reply
437-
438- Returns:
439- IntentMatch if handled otherwise None.
440- """
441- utterances = [item for tup in utterances for item in tup ]
442-
443- # check for conversation time-out
444- skill_timeouts = self .converse_config .get ("skill_timeouts" ) or {}
445- default_timeout = self .converse_config .get ("timeout" , 300 )
446- self .active_skills = [
447- skill for skill in self .active_skills
448- if time .time () - skill [1 ] <= skill_timeouts .get (skill [0 ],
449- default_timeout )]
450-
451- # check if any skill wants to handle utterance
452- for skill in copy (self .active_skills ):
453- if self .do_converse (utterances , skill [0 ], lang , message ):
454- # update timestamp, or there will be a timeout where
455- # intent stops conversing whether its being used or not
456- return IntentMatch ('Converse' , None , None , skill [0 ])
457- return None
458-
459336 def send_complete_intent_failure (self , message ):
460337 """Send a message that no skill could handle the utterance.
461338
@@ -607,7 +484,7 @@ def handle_get_active_skills(self, message):
607484 message: query message to reply to.
608485 """
609486 self .bus .emit (message .reply ("intent.service.active_skills.reply" ,
610- {"skills" : self .active_skills }))
487+ {"skills" : self .converse . active_skills }))
611488
612489 def handle_get_adapt (self , message ):
613490 """handler getting the adapt response for an utterance.
0 commit comments