For easily deployable sample code see the violet-samples project.
Violet provides support for building sophisticated conversational apps/bots on Amazon's Alexa and Google Home. Conversations are built via scripts, and Violet provides a conversation engine that runs as an Alexa Skill.
Support for sophisticated voice apps is supported by allowing:
- for easily building basic logic supported conversation-flows i.e. classically referred to as the view layer in web, mobile, and desktop apps;
- low-level primitives for automated state-management; and
- plugins for moving out significant parts of complexity that support the voice scripts.
- Conversation Flows
- Low-level
- Plugins
- Advanced Topics
- Debugging Conversations
- Contribution/Supporting
This project contains the Violet Conversation Engine that can be used as the basis of your Voice Application.
Every voice script should start will typically start with declaring violet
for use
throughout:
var violet = require('violet').script();
A simple way to respond to a user request (choice):
violet.addFlow(`<app>
<choice>
<expecting>Can you help me</expecting>
<expecting>What can I do</expecting>
<say>
I can help you with your current account balance, with
financial planning, budgeting, investing, or taking out
a loan
</say>
</choice>
</app>`)
If you want to accept data from a user, you will need to declare it with its type and then tell the script engine where to expect it:
violet.addInputTypes({
'name': 'firstName',
});
violet.addFlow(`<app>
<choice>
<expecting>My name is [[name]]</expecting>
<say>I like the name [[name]]</say>
</choice>
</app>`)
To have your script accept multiple inputs from the user, you can combine the <choice>
elements like:
// ...
violet.addFlow(`<app>
<choice>
<expecting>Can you help me</expecting>
<expecting>What can I do</expecting>
<say>
I can help you with your current account balance, with
financial planning, budgeting, investing, or taking out
a loan
</say>
</choice>
<choice>
<expecting>My name is [[name]]</expecting>
<say>I like the name [[name]]</say>
</choice>
</app>`)
Most Voice Apps will want Conversation Flows to be loaded from external files by doing:
violet.loadFlowScript('script.cfl', {app: appController});
The second parameter to loadFlowScript
and addFlow
allows for injecting in a pointer to a controller and other services. This paramater is a list of objects that can be referenced from within a flow script, using either <resolve>
tags or within a <say>
tag using [[expression]]
.
Violet also has a lower level api that voice apps can use. These API's allow you to hook into an automated state management engine (using Goals) so that you don't need to account for states throughout your app.
The Conversational Flow support builds on this low-level API.
Feel free to see tutorials/introduction.js
for an example on how to build a skill.
A simple way to respond to a user request:
violet.respondTo(['Can you help me', 'What can I do'],
(response) => {
response.say(`I can help you with your current account balance, with
financial planning, budgeting, investing, or taking out a
loan`);
});
If you want to accept data from a user, you will need to declare it with its type and then tell the script engine where to expect it:
violet.addInputTypes({
'name': 'firstName',
});
violet.respondTo(['My name is [[name]]'],
(response) => {
response.say('I like the name [[name]]')
});
You can use phrase equivalents to tell the engine that two phrases are identical in all situations:
violet.addPhraseEquivalents([
['My name is', 'I call myself'],
]);
Sophisticated conversations are supported by Violet using goals. When a user indicates a need for a discussion on a particular topic, a script can just add the topic as a goal to be met. Goals could theoretically also be set by a script to proactively check-in with a user or to raise an item related to another goal.
The below simply adds a goal to be met by the system when the user asks for flight arrival information.
violet.respondTo('What time does the [[airline]] flight arrive', 'from [[city]]',
(response) => {
response.addGoal('flightArrivalTime');
});
violet.respondTo('What time does the flight arrive from [[city]]',
(response) => {
response.addGoal('flightArrivalTime');
});
The conversation engine tries to meet goals based on goal definitions provided in the script. Goals are one of two types. The first type of goal is when additional information is needed
violet.defineGoal({
goal: 'flightArrivalTime',
resolve: (response) => {
if (!response.ensureGoalFilled('airline')
|| !response.ensureGoalFilled('city')
|| !response.ensureGoalFilled('flightDay') ) {
return false; // dependent goals not met
}
var airline = response.get('airline');
var city = response.get('city');
var flightDay = response.get('flightDay');
flightArrivalTimeSvc.query(airline, city, flightDay, (arrivalTime)=>{
response.say('Flight ' + airline + ' from ' + city + ' is expected to arrive ' + flightDay + ' at ' + arrivalTime);
});
return true;
}
});
violet.defineGoal({
goal: 'city',
prompt: ['What city do you want the flight to be arriving from'],
respondTo: [{
expecting: '[[city]]',
resolve: (response) => {
response.set('city', response.get('city') );
}}]
});
There are a number of plugins that allow you to further extend the capabilities of Violet Skills
These plugins will allow you to easily store and load data from a data store.
There are two persistence plugins currently provided - these include the Salesforce integration plugin
violetStoreSF
and the Postgres integration plugin
violetStorePG
.
If you are using the Postgres plugin then you will need to set up the DATABASE_URL
environment variable.
If you are using the Salesforce plugin then you will need to set up the following environment variables (locally and on
any deployed platform): V_SFDC_CLIENT_ID
, V_SFDC_CLIENT_SECRET
,
V_SFDC_USERNAME
and V_SFDC_PASSWORD
.
To include the plugin in your code you would need to add this after the violet script:
var violetStoreSF = require('violet/lib/violetStoreSF.js')(violet);
Once included you can store data in the database by using the store
method:
violet.respondTo('I received a bill from [[company]] today for [[amount]]',
(response) => {
response.store('bills',
'user': response.get('userId'),
'from': response.get('company'),
'amount': response.get('amount')
});
response.say('xxxx');
});
To retrieve data from the database you need to use the load method. Caution
Load returns a promise to the DB results - if you need to use the
values retrieved from it, you need to (a) either use a .then
after it OR
(b) you need to make your resolve
method a generator and place a yield
before
the call to the load
method.
// example a:
violet.respondTo(
expecting: 'Who did I receive my bill from most recently?',
resolve: (response) => {
return response.load('bills', 'bills.user', response.get('userId') )
.then((results)=>{
response.say(`You received a bill from ${results[0].from} for ${results[0].amount}`);
});
});
// example b: (note the 'function *' and the 'yield' below)
violet.respondTo(
expecting: 'Who did I receive my bill from most recently?',
resolve: function *(response) {
var results = yield response.load('bills', 'bills.user', response.get('userId') )
response.say(`You received a bill from ${results[0].from} for ${results[0].amount}`);
});
The voice engine can recognize better what the user is saying when it knows to expect one of a limited set of inputs. One helpful way to do this is to use create custom type and have it as an input parameter in your script.
In the below example - from violetTime.js - a custom type called timeUnit
is
declared.
violet.addInputTypes({
"time": "NUMBER",
"timeUnit": {
"type": "timeUnitType",
"values": ["days", "hours", "minutes"]
},
});
After declaring a custom type, the values and the type name need to be provided to Amazon's Skill Configuration Site as a custom slot type.
When developing conversational scripts - it helps to debug/test it in three phrases:
- Make sure the code compiles/runs by typing
npm start
. Fix any errors and keep re-starting the service until misplaced punctuations and declarations have been fixed. - Test the script in the included tester view, by running the script and opening it in a browser, for example: http://localhost:8080/alexa/einstein You will likely want to submit IntentRequest's based on the Utterance's at the bottom of the page. Once you submit a request, verify that the response output SSML is per your needs. Additionally, it is helpful to walk through the script a few times to ensure that the application supports the different user scenarios.
- Once the script works locally, deploy it to the cloud and configure Alexa to talk to the underlying skill using Amazon's Skill Configuration site. At this stage you will likely benefit from testing by iterating rapidly with: invoking the voice-client, examining the conversational-app's logs, and tweaking the utterances in Amazon's Configuration. Testing a voice client is likely best done through a web testing tool like Echosim.io.
Guidelines on contributing to Violet are available here.