hermes-protocol

A JavaScript wrapper around the the Hermes protocol.
Context
The hermes-protocol library provides bindings for the Hermes protocol formely used by Snips components to communicate together. hermes-protocol allows you to interface seamlessly with the Rhasspy and Hermod ecosystems and create Voice applications with ease!
hermes-protocol abstracts away the connection to the MQTT bus and the parsing of incoming and outcoming messages from and to the components of the platform and provides a high-level API as well.
Setup
npm install hermes-protocol
hermes-protocol uses a dynamic library generated by the hermes rust code under the hood.
The installation process will automagically download the file if your os and architecture is supported.
⚠️ Unsupported platforms / architectures
If the setup could not infer the library file version, it will attempt to build it from the sources.
Please note that rust and git are required in order to build the library!
If you want to force this behaviour, you can also define the HERMES_BUILD_FROM_SOURCES environment variable before running npm install.
env HERMES_BUILD_FROM_SOURCES=true npm install hermes-protocol
Usage
Minimal use case
const { withHermes } = require('hermes-protocol')
withHermes(hermes => {
const dialog = hermes.dialog()
dialog.flow('myIntent', (msg, flow) => {
console.log(JSON.stringify(msg))
flow.end()
return `Received message for intent ${myIntent}`
})
})
Expanded use case
const { withHermes } = require('hermes-protocol')
withHermes((hermes, done) => {
const dialog = hermes.dialog()
dialog.on('intent/intentName', message => {
const mySlot = msg.slots.find(slot => slot.slotName === 'slotName')
const slotValue = mySlot.value.value
console.log('Received intent', message.intent.intentName)
if(continueSession) {
dialog.publish('continue_session', {
sessionId: message.sessionId,
text: 'Session continued',
intentFilter: ['nextIntent']
})
} else {
dialog.publish('end_session', {
sessionId: message.sessionId,
text: 'Session ended'
})
}
})
const handler = message => {
dialog.off('intent/someIntent', handler)
}
dialog.on('intent/someIntent', handler)
dialog.once('intent/someIntent', message => {
})
dialog.flow('A', (msg, flow) => {
console.log('Intent A received. Session started.')
flow.continue('B', (msg, flow) => {
console.log('Intent B received. Session continued.')
flow.continue('D', (msg, flow) => {
console.log('Intent D received. Session is ended.')
flow.end()
return 'Finished the session with intent D.'
})
return 'Continue with D.'
})
flow.continue('C', (msg, flow) => {
const slotValue = msg.slots[0].value.value
console.log('Intent C received. Session is ended.')
flow.end()
return 'Finished the session with intent C having value ' + slotValue + ' .'
})
return 'Continue with B or C.'
})
})
API
Sections:
Context loop
An hermes client should implement a context loop that will prevent the program from exiting.
Using withHermes
const { withHermes } = require('hermes-protocol')
const hermesOptions = { }
withHermes((hermes, done) => {
}, hermesOptions)
Instantiate Hermes and use the keepAlive tool
In case you want to create and manage the lifetime of the Hermes instance yourself, you can
use keepAlive and killKeepAlive to prevent the node.js process from exiting.
const { Hermes, tools: { keepAlive, killKeepAlive }} = require('hermes-protocol')
const hermes = new Hermes()
const keepAliveRef = keepAlive(60000)
function done () {
hermes.destroy()
killKeepAlive(keepAliveRef)
}
Hermes class
The Hermes class provides foreign function interface bindings to the Hermes protocol library.
⚠️ Important: Except for very specific use cases, you should have only a single instance of the Hermes class in your program. It can either be provided by the withHermes function OR created by calling new Hermes().
Just keep a single reference to the Hermes instance and pass it around.
The technical reason is that the shared hermes library is read and FFI bindings are created every time you call new Hermes or withHermes, which is really inefficient.
new Hermes({
address: 'localhost:1883',
logs: true,
libraryPath: 'node_modules/hermes-protocol/libhermes_mqtt_ffi',
username: 'user name',
password: 'password',
tls_hostname: 'hostname',
tls_ca_file: [ 'my-cert.cert' ],
tls_ca_path: [ '/ca/path', '/ca/other/path' ],
tls_client_key: 'my-key.key',
tls_client_cert: 'client-cert.cert',
tls_disable_root_store: false
})
dialog()
Use the Dialog Api Subset.
const dialog = hermes.dialog()
injection()
Use the Injection Api Subset.
const injection = hermes.injection()
feedback()
Use the Sound Feedback Api Subset.
const feedback = hermes.feedback()
tts()
Use the text-to-speech Api Subset.
const tts = hermes.tts()
destroy()
Release all the resources associated with this Hermes instance.
hermes.destroy()
Common ApiSubset methods
Check out the hermes protocol documentation for more details on the event names.
on(eventName, listener)
Subscribes to an event on the bus.
const dialog = hermes.dialog()
dialog.on('session_started', message => {
})
once(eventName, listener)
Subscribes to an event on the bus, then unsubscribes after the first event is received.
const dialog = hermes.dialog()
dialog.once('intent/myIntent', message => {
})
off(eventName, listener)
Unsubscribe an already existing event.
const dialog = hermes.dialog()
const handler = message => {
}
dialog.on('intent/myIntent', handler)
dialog.off('intent/myIntent', handler)
publish(eventName, message)
Publish an event programatically.
const { Enums } = require('hermes-protocol/types')
const dialog = hermes.dialog()
dialog.publish('start_session', {
customData: 'some data',
siteId: 'site Id',
init: {
type: Enums.initType.notification,
text: 'hello world'
}
})
Dialog Api Subset
The dialog manager.
Events available for publishing
Start a new dialog session.
const { Enums } = require('hermes-protocol/types')
dialog.publish('start_session', {
customData: ,
siteId: ,
init: {
type: Enums.initType.notification,
text:
}
})
dialog.publish('start_session', {
customData: ,
siteId: ,
init: {
type: Enums.initType.action,
text: ,
intentFilter: ,
canBeEnqueued: ,
sendIntentNotRecognized:
}
})
Continue a dialog session.
dialog.publish('continue_session', {
sessionId: ,
text: ,
intentFilter: ,
customData: ,
sendIntentNotRecognized: ,
slot:
})
Finish a dialog session.
dialog.publish('end_session', {
sessionId: ,
text:
})
Configure intents that can trigger a session start.
dialog.publish('configure', {
siteId: ,
intents: [{
intentId: ,
enable:
}]
})
Events available for subscribing
An intent was recognized.
A dialog session has ended.
A dialog session has been put in the queue.
A dialog session has started.
No intents were recognized.
Note that the dialog session must have been started or continued with the sendIntentNotRecognized flag in order for this to work.
DialogFlow
The Dialog API Subset exposes a small API that makes managing complex dialog flows a breeze.
flow(intent, action)
Starts a new dialog flow.
const dialog = hermes.dialog()
dialog.flow('intentName', (message, flow) => {
return 'intentName recognized!'
})
dialog.flow('intentName', (message, flow) => {
return {
text: 'intentName recognized!'
}
})
dialog.flow('intentName', async (message, flow) => {
const json = await fetch('something').then(res => res.json())
return 'Fetched some stuff!'
})
flows([{ intent, action }])
Same as flow(), but with multiple starting intents.
Useful when designing speech patterns with loops ((intentOne or intentTwo) -> intentTwo -> intentOne -> intentTwo -> ...,
so that the starting intents callbacks will not get called multiple times if a session is already in progress.
const intents = [
{
intent: 'intentOne',
action: (msg, flow) => { }
},
{
intent: 'intentTwo',
action: (msg, flow) => { }
}
]
dialog.flows(intents)
sessionFlow(id, action)
Advanced, for basic purposes use flow() or flows().
Creates a dialog flow that will trigger when the target session starts.
Useful when initiating a session programmatically.
dialog.sessionFlow('a_unique_id', (msg, flow) => {
})
flow.continue(intentName, action, { slotFiller })
Subscribes to an intent for the next dialog step.
dialog.flow('intentName', async (message, flow) => {
flow.continue('otherIntent', (message, flow) => {
})
flow.continue('andAnotherIntent', (message, flow) => {
})
return 'Continue with either one of these 2 intents.'
})
About the slotFiller option
Set the slot filler for the current dialogue round with a given slot name.
Requires flow.continue() to be called exactly once in the current round.
If set, the dialogue engine will not run the the intent classification on the user response and go straight to
slot filling, assuming the intent is the one passed in the continue, and searching the value of the given slot.
flow.continue('myIntent', (message, flow) => {
}, { slotFiller: 'slotName' })
flow.notRecognized(action)
Add a callback that is going to be executed if the intents failed to be recognized.
dialog.flow('intentName', async (message, flow) => {
flow.notRecognized((message, flow) => {
})
return 'If the dialog failed to understand the intents, notRecognized callback will be called.'
})
flow.end()
Ends the dialog flow.
dialog.flow('intentName', async (message, flow) => {
flow.end()
return 'Dialog ended.'
})
Injection Api Subset
Vocabulary injection for the speech recognition.
Events available for publishing
Requests custom payload to be injected.
const { Enums } = require('hermes-protocol/types')
injection.publish('injection_request', {
id: ,
crossLanguage: ,
operations: [
[
Enums.injectionKind.add,
{
films : [
'The Wolf of Wall Street',
'The Lord of the Rings'
]
}
]
],
lexicon: {}
})
Will request that a new status message will be sent.
Note that you should subscribe to injection_status beforehand in order to receive the message.
injection.publish('injection_status_request')
Will clear all the previously injected values.
injection.publish('injection_reset_request', {
requestId:
})
Events available for subscribing
Get the status of the last injection request.
When an injection request completes.
When an injection reset request completes.
Feedback Api Subset
Control the sound feedback.
Events available for publishing
Turn the notification sound on.
feedback.publish('notification_on', {
"siteId": ,
"sessionId":
})
Turn the notification sound off.
feedback.publish('notification_off', {
"siteId": ,
"sessionId":
})
TTS Api Subset
Exposes text-to-speech options.
Events available for publishing
Register a sound file and makes the TTS able to play it in addition to pure speech.
You can interpolate a text-to-speech string with the following tag: [[sound:soundId]]
const wavBuffer =
tts.publish('register_sound', {
soundId: ,
wavSound: wavBuffer.toString('base64')
})
License
Apache 2.0/MIT
Licensed under either of
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.