A Node.js server for your Google Assistant (and Google Home).
npm i -S @manekinekko/google-actions-server
$ yarn add @manekinekko/google-actions-server
See the GAS Starter for more details.
Here is the simplest guide in order to use GAS:
import { ActionServer } from '@manekinekko/google-actions-server';
const agent = new ActionServer();
agent.welcome((assistant) => {
agent.ask('Welcome Home. How can I help');
});
agent.intent(ActionServer.intent.action.TEXT, (assistant) => {
// reads the user's request
const userInput = assistant.getRawInput();
})
// start listening for commands (on port 8080)
agent.listen();
Available intents are:
This intent is triggered when users invoke your action by name, such as "talk to ". This intent si required for every action package.
This intent is triggered when users speak input and you receive the input as a request to your fulfillment endpoint.
Triggered if Google Assistant needs to ask the user for more permissions (not implemented yet by GAS)
Create an HTTP server and set it to listen on port port
(default: 8080).
Calling new ActionServer()
will return an Agent
instance, which exposes the following API:
Register a callback
with an intent of type ActionServer.intent.action.*
. When invoked, the callback
function receives an instance of ActionsSdkAssistant. For instance:
agent.welcome(ActionServer.intent.action.TEXT, (assistant) => {
// assistant
});
Register a callback
with the ActionServer.intent.action.MAIN intent. This is the same as:
agent.intent(ActionServer.intent.action.MAIN, (assistant) => {
// assistant
});
Register a callback
with the ActionServer.intent.action.MAIN intent. This is the same as:
Starts the GAS server on port 8080
(the default port). This method must be called after you have registered all of your intents.
A convenient method that abstracts away the assistant.ask()
configuration. Typically, with assistant.ask()
you have to:
const inputPrompt = assistant.buildInputPrompt(isSsml, message, noInputs);
assistant.data = previousState;
assistant.ask(inputPrompt);
With agent.ask()
, you can just pass it the message:
agent.ask(message);
Of course, you can still call assistant.ask()
if you want to provide a configuration.
A convenient method to set a list of greeting messages that you will play randomly to the user.
A convenient method to get a random greeting message set using agent.setGreetings()
.
A convenient method that will trigger a random greeting message for you.
A convenient method to set a list of random conversations asking the user for another query. For instant: How can I help?
or Do you have another request?
.
A convenient method to get a random conversation message set using agent.setConversationMessages()
.
This method is used to provide a set of data that will be used by agent.matchUserRequest()
. This could be the data that will be used by the assistant to interact with the user, or a set of Hot Words such as:
this.agent.train('hot_words', [
'what time is it?',
'tell me the time',
`what's the time`,
//...
]);
Here is another use case, let's say you want to provide a decision list:
const decisionList = [
{
'scenario': 'I have one existing Observable, and I want to change each emitted value to be a constant value',
'operator': 'mapTo'
},
{
'scenario': 'I have one existing Observable, and I want to change each emitted value to be a value calculated through a formula',
'operator': 'map'
},
...
];
You would then provide it to GAS like so:
const fields = ['operator', 'scenario'];
this.agent.train('decision_list', decisionList, fields);
Please note that the fields
option has been provided since the items inside this decision list are stored in key/value pairs.
IMPORTANT: the first field (in the sample above: operator
) will be used as the reference field: the field that will be returned by agent.matchUserRequest()
.
NOTE: GAS uses elasticlunr that performs the Full-Text Search technique to retrieve the matched documents.
Use this method to match the user's input with a set of data provided in agent.train()
.
For instance, let's say you have the decisionList
list from the example above; in order to find a matched document you would call agent.matchUserRequest()
like so:
// get the user's input
const rawInput = assistant.getRawInput();
// provide a lookup configuration (see below)
const lookupOptions = {...};
// the callback that will be called
const responseCallback = (foundDocuments, rawInput) => { ... };
// run the search operation
const matchedDocuments = this.agent.matchUserRequest('decision_list', rawInput, responseCallback, lookupOptions);
The matchedDocuments
would then contain the matched documents based on the rawInput
string. In some case you may get false-positives, documents that are not relevant to the intended search question.
In this case, you may provide a lookupOptions
object in order to configure/tweak the search operation.
NOTE: if no documents are found, an empty array is returned.
You can provide the threshold
property to filter out the false-positives. threshold
can be either a Number or a Function.
Using a number:
const lookupOptions = {
threshold: 0.6
};
When using a function, it will be provided with the current entry from the data set. You can access the score
property on that entry:
const lookupOptions = {
threshold: entry => {
return entry.score > 0.6;
}
};
The fields property is a elasticlunr configuration query. For instance, we could provide the following configuration for the example from above:
const lookupOptions = {
fields: {
scenario: {
boost: 2,
bool: 'AND',
expand: true
},
operator: {
boost: 1
}
}
};
See elasticlunr configuration query for more details.
A convenient method to fetch a remote document. Useful if you want to get more information from a remote website. For instance:
agent.fetch(url, (error, content) => {
if (error) {
console.error(error);
}
else {
console.log(content);
}
});
The current implementation is still a POC. It's obviously missing lots of features. Please open issues to discuss any feature you want to be added, submit suggestions, or anything else... All contributions are welcome ;)