forked from platypush/platypush
Several improvements for assistant
plugins.
- `stop_conversation_on_speech_match` should default to True. - `render_response` should also handle conversation follow-ups, set the follow-up to True if the response ends with a question mark and the value of `with_follow_on_turn` is not set, - Don't render responses if a `tts_plugin` is not set.
This commit is contained in:
parent
c7d640a1d2
commit
fcae7aa3ad
3 changed files with 78 additions and 36 deletions
|
@ -147,7 +147,12 @@ class SpeechRecognizedEvent(AssistantEvent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = super().matches_condition(condition)
|
result = super().matches_condition(condition)
|
||||||
if result.is_match and self.assistant and condition.args.get('phrase'):
|
if (
|
||||||
|
result.is_match
|
||||||
|
and condition.args.get('phrase')
|
||||||
|
and self.assistant
|
||||||
|
and self.assistant.stop_conversation_on_speech_match
|
||||||
|
):
|
||||||
self.assistant.stop_conversation()
|
self.assistant.stop_conversation()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -248,7 +253,11 @@ class IntentRecognizedEvent(AssistantEvent):
|
||||||
default assistant response if the event matched some event hook condition.
|
default assistant response if the event matched some event hook condition.
|
||||||
"""
|
"""
|
||||||
result = super().matches_condition(condition)
|
result = super().matches_condition(condition)
|
||||||
if result.is_match and self.assistant:
|
if (
|
||||||
|
result.is_match
|
||||||
|
and self.assistant
|
||||||
|
and self.assistant.stop_conversation_on_speech_match
|
||||||
|
):
|
||||||
self.assistant.stop_conversation()
|
self.assistant.stop_conversation()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -50,7 +50,7 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
|
||||||
tts_plugin: Optional[str] = None,
|
tts_plugin: Optional[str] = None,
|
||||||
tts_plugin_args: Optional[Dict[str, Any]] = None,
|
tts_plugin_args: Optional[Dict[str, Any]] = None,
|
||||||
conversation_start_sound: Optional[str] = None,
|
conversation_start_sound: Optional[str] = None,
|
||||||
stop_conversation_on_speech_match: bool = False,
|
stop_conversation_on_speech_match: bool = True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
@ -66,13 +66,21 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
|
||||||
on the default audio output device. If not set, the assistant won't
|
on the default audio output device. If not set, the assistant won't
|
||||||
play any sound when it detects a speech.
|
play any sound when it detects a speech.
|
||||||
|
|
||||||
:param stop_conversation_on_speech_match: If set, the plugin will close the
|
:param stop_conversation_on_speech_match: If set, the plugin will
|
||||||
conversation if the latest recognized speech matches a registered
|
prevent the default assistant response when a
|
||||||
:class:`platypush.message.event.assistant.SpeechRecognizedEvent` hook
|
:class:`platypush.message.event.assistant.SpeechRecognizedEvent`
|
||||||
with a phrase. This is usually set to ``True`` for
|
matches a user hook with a condition on a ``phrase`` field. This is
|
||||||
:class:`platypush.plugins.assistant.google.GoogleAssistantPlugin`,
|
useful to prevent the assistant from responding with a default "*I'm
|
||||||
as it overrides the default assistant response when a speech event is
|
sorry, I can't help you with that*" when e.g. you say "*play the
|
||||||
actually handled on the application side.
|
music*", and you have a hook that matches the phrase "*play the
|
||||||
|
music*" and handles it with a custom action. If set, and you wish
|
||||||
|
the assistant to also provide an answer if an event matches one of
|
||||||
|
your hooks, then you should call the :meth:`render_response` method
|
||||||
|
in your hook handler. If not set, then the assistant will always try
|
||||||
|
and respond with a default message, even if a speech event matches
|
||||||
|
the phrase of one of your hooks. In this case, if you want to prevent
|
||||||
|
the default response, you should call :meth:`stop_conversation`
|
||||||
|
explicitly from your hook handler. Default: True.
|
||||||
"""
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.tts_plugin = tts_plugin
|
self.tts_plugin = tts_plugin
|
||||||
|
@ -165,15 +173,38 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
|
||||||
return asdict(self._state)
|
return asdict(self._state)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def render_response(self, text: str, *_, **__):
|
def render_response(
|
||||||
|
self, text: str, *_, with_follow_on_turn: Optional[bool] = None, **__
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Render a response text as audio over the configured TTS plugin.
|
Render a response text as audio over the configured TTS plugin.
|
||||||
|
|
||||||
:param text: Text to render.
|
:param text: Text to render.
|
||||||
|
:param with_follow_on_turn: If set, the assistant will wait for a follow-up.
|
||||||
|
By default, ``with_follow_on_turn`` will be automatically set to true if
|
||||||
|
the ``text`` ends with a question mark.
|
||||||
|
:return: True if the assistant is waiting for a follow-up, False otherwise.
|
||||||
"""
|
"""
|
||||||
self._on_response_render_start(text)
|
if not text:
|
||||||
|
self._on_no_response()
|
||||||
|
return False
|
||||||
|
|
||||||
|
follow_up = (
|
||||||
|
bool(text and text.strip().endswith('?'))
|
||||||
|
if with_follow_on_turn is None
|
||||||
|
else with_follow_on_turn
|
||||||
|
)
|
||||||
|
|
||||||
|
self._on_response_render_start(text, with_follow_on_turn=follow_up)
|
||||||
self._render_response(text)
|
self._render_response(text)
|
||||||
self._on_response_render_end()
|
self._on_response_render_end(with_follow_on_turn=follow_up)
|
||||||
|
|
||||||
|
if follow_up:
|
||||||
|
self.start_conversation()
|
||||||
|
else:
|
||||||
|
self.stop_conversation()
|
||||||
|
|
||||||
|
return follow_up
|
||||||
|
|
||||||
def _get_tts_plugin(self):
|
def _get_tts_plugin(self):
|
||||||
if not self.tts_plugin:
|
if not self.tts_plugin:
|
||||||
|
@ -228,22 +259,37 @@ class AssistantPlugin(Plugin, AssistantEntityManager, ABC):
|
||||||
self._conversation_running.clear()
|
self._conversation_running.clear()
|
||||||
self._send_event(NoResponseEvent)
|
self._send_event(NoResponseEvent)
|
||||||
|
|
||||||
def _on_response_render_start(self, text: Optional[str]):
|
def _on_response_render_start(
|
||||||
|
self, text: Optional[str], with_follow_on_turn: bool = False
|
||||||
|
):
|
||||||
from platypush.message.event.assistant import ResponseEvent
|
from platypush.message.event.assistant import ResponseEvent
|
||||||
|
|
||||||
self._last_response = text
|
self._last_response = text
|
||||||
self._send_event(ResponseEvent, response_text=text)
|
self._send_event(
|
||||||
|
ResponseEvent, response_text=text, with_follow_on_turn=with_follow_on_turn
|
||||||
|
)
|
||||||
|
|
||||||
def _render_response(self, text: Optional[str]):
|
def _render_response(self, text: Optional[str]):
|
||||||
tts = self._get_tts_plugin()
|
if not text:
|
||||||
if tts and text:
|
return
|
||||||
self.stop_conversation()
|
|
||||||
tts.say(text=text, **self.tts_plugin_args)
|
|
||||||
|
|
||||||
def _on_response_render_end(self):
|
tts = self._get_tts_plugin()
|
||||||
|
if not tts:
|
||||||
|
self.logger.warning(
|
||||||
|
'Got a response to render, but no TTS plugin is configured: %s', text
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
tts.say(text=text, **self.tts_plugin_args)
|
||||||
|
|
||||||
|
def _on_response_render_end(self, with_follow_on_turn: bool = False):
|
||||||
from platypush.message.event.assistant import ResponseEndEvent
|
from platypush.message.event.assistant import ResponseEndEvent
|
||||||
|
|
||||||
self._send_event(ResponseEndEvent, response_text=self._last_response)
|
self._send_event(
|
||||||
|
ResponseEndEvent,
|
||||||
|
response_text=self._last_response,
|
||||||
|
with_follow_on_turn=with_follow_on_turn,
|
||||||
|
)
|
||||||
|
|
||||||
def _on_hotword_detected(self, hotword: Optional[str]):
|
def _on_hotword_detected(self, hotword: Optional[str]):
|
||||||
from platypush.message.event.assistant import HotwordDetectedEvent
|
from platypush.message.event.assistant import HotwordDetectedEvent
|
||||||
|
|
|
@ -76,7 +76,6 @@ class AssistantGooglePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
self,
|
self,
|
||||||
credentials_file: Optional[str] = None,
|
credentials_file: Optional[str] = None,
|
||||||
device_model_id: str = 'Platypush',
|
device_model_id: str = 'Platypush',
|
||||||
stop_conversation_on_speech_match: bool = True,
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
@ -95,20 +94,8 @@ class AssistantGooglePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
:param device_model_id: The device model ID that identifies the device
|
:param device_model_id: The device model ID that identifies the device
|
||||||
where the assistant is running (default: Platypush). It can be a
|
where the assistant is running (default: Platypush). It can be a
|
||||||
custom string.
|
custom string.
|
||||||
|
|
||||||
:param stop_conversation_on_speech_match: If set, the plugin will close the
|
|
||||||
conversation if the latest recognized speech matches a registered
|
|
||||||
:class:`platypush.message.event.assistant.SpeechRecognizedEvent` hook
|
|
||||||
with a phrase. This is usually set to ``True`` for
|
|
||||||
:class:`platypush.plugins.assistant.google.GoogleAssistantPlugin`,
|
|
||||||
as it overrides the default assistant response when a speech event is
|
|
||||||
actually handled on the application side.
|
|
||||||
"""
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
super().__init__(
|
|
||||||
stop_conversation_on_speech_match=stop_conversation_on_speech_match,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
self._credentials_file = credentials_file
|
self._credentials_file = credentials_file
|
||||||
self.device_model_id = device_model_id
|
self.device_model_id = device_model_id
|
||||||
self.credentials = None
|
self.credentials = None
|
||||||
|
@ -155,7 +142,7 @@ class AssistantGooglePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
hasattr(EventType, 'ON_RENDER_RESPONSE')
|
hasattr(EventType, 'ON_RENDER_RESPONSE')
|
||||||
and event.type == EventType.ON_RENDER_RESPONSE
|
and event.type == EventType.ON_RENDER_RESPONSE
|
||||||
):
|
):
|
||||||
self._on_reponse_rendered(event.args.get('text'))
|
self._on_response_render_start(event.args.get('text'))
|
||||||
elif (
|
elif (
|
||||||
hasattr(EventType, 'ON_RESPONDING_STARTED')
|
hasattr(EventType, 'ON_RESPONDING_STARTED')
|
||||||
and event.type == EventType.ON_RESPONDING_STARTED
|
and event.type == EventType.ON_RESPONDING_STARTED
|
||||||
|
|
Loading…
Reference in a new issue