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

jasper-dev: text-to-intent mapping #511

Open
wants to merge 3 commits into
base: jasper-dev
Choose a base branch
from
Open
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
72 changes: 46 additions & 26 deletions jasper/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,18 @@ def __init__(self, use_mic=USE_STANDARD_MIC, batch_file=None):
tts_slug = self.config['tts_engine']
except KeyError:
tts_slug = 'espeak-tts'
self._logger.warning("tts_engine not specified in profile, using" +
self._logger.warning("tts_engine not specified in profile, using " +
"defaults.")
self._logger.debug("Using TTS engine '%s'", tts_slug)

try:
tti_slug = self.config['tti_engine']
except KeyError:
tti_slug = 'phrasematcher-tti'
self._logger.warning("tti_engine not specified in profile, using " +
"defaults.")
self._logger.debug("Using TTI engine '%s'", tti_slug)

try:
keyword = self.config['keyword']
except KeyError:
Expand Down Expand Up @@ -172,33 +180,11 @@ def __init__(self, use_mic=USE_STANDARD_MIC, batch_file=None):
', '.join(devices))
raise

# Initialize Brain
self.brain = brain.Brain(self.config)
for info in self.plugins.get_plugins_by_category('speechhandler'):
try:
plugin = info.plugin_class(info, self.config)
except Exception as e:
self._logger.warning(
"Plugin '%s' skipped! (Reason: %s)", info.name,
e.message if hasattr(e, 'message') else 'Unknown',
exc_info=(
self._logger.getEffectiveLevel() == logging.DEBUG))
else:
self.brain.add_plugin(plugin)

if len(self.brain.get_plugins()) == 0:
msg = 'No plugins for handling speech found!'
self._logger.error(msg)
raise RuntimeError(msg)
elif len(self.brain.get_all_phrases()) == 0:
msg = 'No command phrases found!'
self._logger.error(msg)
raise RuntimeError(msg)

# create instanz of SST and TTS
active_stt_plugin_info = self.plugins.get_plugin(
active_stt_slug, category='stt')
active_stt_plugin = active_stt_plugin_info.plugin_class(
'default', self.brain.get_plugin_phrases(), active_stt_plugin_info,
active_stt_plugin_info,
self.config)

if passive_stt_slug != active_stt_slug:
Expand All @@ -208,7 +194,6 @@ def __init__(self, use_mic=USE_STANDARD_MIC, batch_file=None):
passive_stt_plugin_info = active_stt_plugin_info

passive_stt_plugin = passive_stt_plugin_info.plugin_class(
'keyword', self.brain.get_standard_phrases() + [keyword],
passive_stt_plugin_info, self.config)

tts_plugin_info = self.plugins.get_plugin(tts_slug, category='tts')
Expand All @@ -229,6 +214,41 @@ def __init__(self, use_mic=USE_STANDARD_MIC, batch_file=None):
passive_stt_plugin, active_stt_plugin,
tts_plugin, self.config, keyword=keyword)

# Text-to-intent handler
tti_plugin_info = self.plugins.get_plugin(tti_slug, category='tti')
#tti_plugin = tti_plugin_info.plugin_class(tti_plugin_info, self.config)

# Initialize Brain
self.brain = brain.Brain(self.config,
tti_plugin_info.plugin_class(tti_plugin_info, self.config))
for info in self.plugins.get_plugins_by_category('speechhandler'):
# create instanz
try:
plugin = info.plugin_class(info, self.config,
tti_plugin_info.plugin_class(tti_plugin_info, self.config), self.mic)
except Exception as e:
self._logger.warning(
"Plugin '%s' skipped! (Reason: %s)", info.name,
e.message if hasattr(e, 'message') else 'Unknown',
exc_info=(
self._logger.getEffectiveLevel() == logging.DEBUG))
else:
self.brain.add_plugin(plugin)

if len(self.brain.get_plugins()) == 0:
msg = 'No plugins for handling speech found!'
self._logger.error(msg)
raise RuntimeError(msg)
elif len(self.brain.get_all_phrases()) == 0:
msg = 'No command phrases found!'
self._logger.error(msg)
raise RuntimeError(msg)

# init SSTs and compile vocabulary if needed
active_stt_plugin.init('default', self.brain.get_plugin_phrases())
passive_stt_plugin.init('keyword', self.brain.get_standard_phrases() + [keyword])

# Initialize Conversation
self.conversation = conversation.Conversation(
self.mic, self.brain, self.config)

Expand Down
8 changes: 5 additions & 3 deletions jasper/brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@


class Brain(object):
def __init__(self, config):
def __init__(self, config, tti_plugin):
"""
Instantiates a new Brain object, which cross-references user
input with a list of modules. Note that the order of brain.modules
matters, as the Brain will return the first module
input with a list of modules. Note that the order of brain.plugins
matters, as the Brain will return the first plugin
that accepts a given input.
"""

self._plugins = []
self._logger = logging.getLogger(__name__)
self._config = config
self._tti_plugin = tti_plugin
#self._tti = tti_plugin_info.plugin_class(tti_plugin_info, self._config)

def add_plugin(self, plugin):
self._plugins.append(plugin)
Expand Down
80 changes: 71 additions & 9 deletions jasper/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,37 +29,99 @@ class AudioEnginePlugin(GenericPlugin, audioengine.AudioEngine):


class SpeechHandlerPlugin(GenericPlugin, i18n.GettextMixin):
"""
Generic parent class for SpeechHandlingPlugins
"""
__metaclass__ = abc.ABCMeta

def __init__(self, *args, **kwargs):
GenericPlugin.__init__(self, *args, **kwargs)
def __init__(self, info, config, tti_plugin, mic):
"""
Instantiates a new generic SpeechhandlerPlugin instance. Requires a tti_plugin and a mic
instance.
"""
GenericPlugin.__init__(self, info, config)
i18n.GettextMixin.__init__(
self, self.info.translations, self.profile)
self._tti_plugin = tti_plugin
#self._tti_plugin = tti_plugin_info.plugin_class(tti_plugin_info, self._plugin_config)
self._mic = mic

# @classmethod
# def init(self, *args, **kwargs):
# """
# Initiate Plugin, e.g. do some runtime preparation stuff
#
# Arguments:
# """
# self._tti_plugin.init(self, *args, **kwargs)

@abc.abstractmethod
@classmethod
def get_phrases(self):
pass
return self._tti_plugin.get_phrases(self)

@classmethod
@abc.abstractmethod
def handle(self, text, mic):
pass

@abc.abstractmethod
@classmethod
def is_valid(self, text):
pass
return self._tti_plugin.is_valid(self, text)

@classmethod
def check_phrase(self, text):
return self._tti_plugin.get_confidence(self, text)

def get_priority(self):
return 0


class TTIPlugin(GenericPlugin):
"""
Generic parent class for text-to-intent handler
"""
__metaclass__ = abc.ABCMeta
ACTIONS = []
WORDS = {}

def __init__(self, *args, **kwargs):
GenericPlugin.__init__(self, *args, **kwargs)

@classmethod
@abc.abstractmethod
def get_phrases(cls):
pass

@classmethod
@abc.abstractmethod
def get_intent(cls, phrase):
pass

@abc.abstractmethod
def is_valid(self, phrase):
pass

@classmethod
def get_confidence(self, phrase):
return self.is_valid(self, phrase)

@abc.abstractmethod
def get_actionlist(self, phrase):
pass


class STTPlugin(GenericPlugin):
def __init__(self, name, phrases, *args, **kwargs):
def __init__(self, *args, **kwargs):
GenericPlugin.__init__(self, *args, **kwargs)
self._vocabulary_phrases = phrases
self._vocabulary_name = name
self._vocabulary_phrases = None
self._vocabulary_name = None
self._vocabulary_compiled = False
self._vocabulary_path = None

def init(self, name, phrases):
self._vocabulary_phrases = phrases
self._vocabulary_name = name

def compile_vocabulary(self, compilation_func):
if self._vocabulary_compiled:
raise RuntimeError("Vocabulary has already been compiled!")
Expand Down
3 changes: 2 additions & 1 deletion jasper/pluginstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ def __init__(self, plugin_dirs):
'audioengine': plugin.AudioEnginePlugin,
'speechhandler': plugin.SpeechHandlerPlugin,
'tts': plugin.TTSPlugin,
'stt': plugin.STTPlugin
'stt': plugin.STTPlugin,
'tti': plugin.TTIPlugin
}

def detect_plugins(self):
Expand Down
13 changes: 11 additions & 2 deletions jasper/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
}


class TestTTI(object):
def __init__(self):
pass


class TestMic(object):
def __init__(self, inputs=[]):
self.inputs = inputs
Expand All @@ -31,12 +36,16 @@ def say(self, phrase):
self.outputs.append(phrase)


def get_plugin_instance(plugin_class, *extra_args):
def get_genericplugin_instance(plugin_class, *extra_args):
info = type('', (object,), {
'name': 'pluginunittest',
'translations': {
'en-US': gettext.NullTranslations()
}
})()
args = tuple(extra_args) + (info, TEST_PROFILE)
args = (info, TEST_PROFILE)+tuple(extra_args)
return plugin_class(*args)


def get_plugin_instance(plugin_class, *extra_args):
return get_genericplugin_instance(plugin_class, TestTTI(), TestMic())
21 changes: 21 additions & 0 deletions plugins/speechhandler/clock/clock.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
class ClockPlugin(plugin.SpeechHandlerPlugin):
def get_phrases(self):
return [self.gettext("TIME")]

def init(self):
SpeechHandlerPlugin.init(self)
self._tti.ACTIONS = [('WHAT TIME IS IT',self.say_time)]

def handle(self, text, mic):
"""
Expand All @@ -25,6 +29,23 @@ def handle(self, text, mic):
fmt = "It is {t:%l}:{t.minute} {t:%P} right now."
mic.say(self.gettext(fmt).format(t=now))

def say_time(self):
"""
Reports the current time based on the user's timezone.

Arguments:
text -- user-input, typically transcribed speech
mic -- used to interact with the user (for both input and output)
"""

tz = app_utils.get_timezone(self.profile)
now = datetime.datetime.now(tz=tz)
if now.minute == 0:
fmt = "It is {t:%l} {t:%P} right now."
else:
fmt = "It is {t:%l}:{t.minute} {t:%P} right now."
mic.say(self.gettext(fmt).format(t=now))

def is_valid(self, text):
"""
Returns True if input is related to the time.
Expand Down
4 changes: 4 additions & 0 deletions plugins/stt/julius-stt/julius.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def __init__(self, *args, **kwargs):
self._logger.warning("This STT plugin doesn't have multilanguage " +
"support!")


def init(self, *args, **kwargs):
plugin.STTPlugin.init(self, *args, **kwargs)

vocabulary_path = self.compile_vocabulary(
juliusvocab.compile_vocabulary)

Expand Down
3 changes: 3 additions & 0 deletions plugins/stt/pocketsphinx-stt/sphinxplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ def __init__(self, *args, **kwargs):
self._logger.warning("This STT plugin doesn't have multilanguage " +
"support!")

def init(self, *args, **kwargs):
plugin.STTPlugin.init(self, *args, **kwargs)

vocabulary_path = self.compile_vocabulary(
sphinxvocab.compile_vocabulary)

Expand Down
12 changes: 6 additions & 6 deletions plugins/stt/pocketsphinx-stt/tests/test_sphinxplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ def setUp(self):
self.time_clip = paths.data('audio', 'time.wav')

try:
self.passive_stt_engine = testutils.get_plugin_instance(
sphinxplugin.PocketsphinxSTTPlugin,
'unittest-passive', ['JASPER'])
self.active_stt_engine = testutils.get_plugin_instance(
sphinxplugin.PocketSphinxSTTPlugin,
'unittest-active', ['TIME'])
self.passive_stt_engine = testutils.get_genericplugin_instance(
sphinxplugin.PocketsphinxSTTPlugin)
self.passive_stt_engine.init('unittest-passive', ['JASPER'])
self.active_stt_engine = testutils.get_genericplugin_instance(
sphinxplugin.PocketSphinxSTTPlugin)
self.active_stt_engine.init('unittest-active', ['TIME'])
except ImportError:
self.skipTest("Pockersphinx not installed!")

Expand Down
2 changes: 2 additions & 0 deletions plugins/tti/phrasematcher-tti/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from .phrasematcher import PhraseMatcherPlugin
Loading