Python template

This template is made for python >= 3.7

Previous versions:

python3.5 template

python2.7 template

You may notice that there are two templates repositories corresponding to python:

Both templates are good. The former one is made for connecting to snippets, you can use it to build action for a single intent easily. The later one is modified from the 1st one but using a richer action code structure.

The below documentation is for snips-app-template-py.

Template Organisation

Files listed below are required as a minimum construction, which ensures that this action code can be managed by snips-skill-server. But it does not mean you should only have these files. With a simple action, it can be written in the action-app_example.py file. However with some more complicated action code, it's better to have a specific class file for it.

└── snips-app-template-py
├── action-app_template.py # main handler for intents
├── snipsTools.py # some useful tools
├── config.ini.default # default app configuration
├── requirements.txt # required dependencies
└── setup.sh # setup script

Files Explanation in Detail

action-app_template.py

This is the file used to bind your action codes with MQTT bus. It helps to read the configuration file, setup MQTT connection, subscribe to Specific topics and setup callback functions.

A simplified code is shown below:

action-app_template.py
#!/usr/bin/env python3.7
from snipsTools import SnipsConfigParser
from hermes_python.hermes import Hermes
# imported to get type check and IDE completion
from hermes_python.ontology.dialogue.intent import IntentMessage
CONFIG_INI = "config.ini"
# if this skill is supposed to run on the satellite,
# please get this mqtt connection info from <config.ini>
#
# hint: MQTT server is always running on the master device
MQTT_IP_ADDR: str = "localhost"
MQTT_PORT: int = 1883
MQTT_ADDR: str = "{}:{}".format(MQTT_IP_ADDR, str(MQTT_PORT))
class Template:
"""class used to wrap action code with mqtt connection
please change the name referring to your application
"""
def __init__(self):
# get the configuration if needed
try:
self.config = SnipsConfigParser.read_configuration_file(CONFIG_INI)
except Exception:
self.config = None
# start listening to MQTT
self.start_blocking()
@staticmethod
def intent_1_callback(hermes: Hermes,
intent_message: IntentMessage):
# terminate the session first if not continue
hermes.publish_end_session(intent_message.session_id, "")
# action code goes here...
print('[Received] intent: {}'.format(
intent_message.intent.intent_name))
# if need to speak the execution result by tts
hermes.publish_start_session_notification(
intent_message.site_id,
"Action 1", "")
@staticmethod
def intent_2_callback(hermes: Hermes,
intent_message: IntentMessage):
# terminate the session first if not continue
hermes.publish_end_session(intent_message.session_id, "")
# action code goes here...
print('[Received] intent: {}'.format(
intent_message.intent.intent_name))
# if need to speak the execution result by tts
hermes.publish_start_session_notification(
intent_message.site_id,
"Action 2", "")
# register callback function to its intent and start listen to MQTT bus
def start_blocking(self):
with Hermes(MQTT_ADDR) as h:
h.subscribe_intent('intent_1', self.intent_1_callback)\
.subscribe_intent('intent_2', self.intent_2_callback)\
.loop_forever()
if __name__ == "__main__":
Template()

The beginning is similar to most Python codes, it imports all the necessary dependencies / modules. It also defines the config file name (Usually set to config.ini and put this file as the same directory with this code file) and MQTT connection info. If the App you are making is supposed to run on a satellite or some other devices, we recommend that the MQTT connection info should be loaded from the external config.ini file instead of fixing it in the code.

The main part of this code is composed of one class - Template, which is used to bind App related action code with MQTT bus. This class should be named corresponding to the App.

The code is mainly composed by different intent callback functions such as intent_1_callback, inent_2_callback. Inside each callback function is the place to write the intent related action code.

For each sub callback function, it's better to terminate the session first if there is no need continuing it. This can prevent other snips components(Like dialog-manager, hotword..) from being blocked by the action code.

start_blocking() is used to register callback functions with its associated intents then starts to listen on MQTT bus.

At the beginning of __init__(), SnipsConfigParser is called to provide a configuration dictionary. This part is not mandatory and can be removed if not needed.

snipsTools.py

This file provides some common useful class but is not part of the action code. For the moment, it only has the SnipsConfigParser.

read_configuration_file(configuration_file)

Read configuration file and return a dictionary.
:param configuration_file: configuration file. E.g. "config.ini".
:return: the dictionary representation of the config file.

write_configuration_file(configuration_file, data)

Write configuration dictionary to config file.
:param configuration_file: configuration file. E.g. "config.ini".
:data: the dictionary contains the data to save.
:return: False if failed to write.

config.ini

This is the file used to save action code configurations. An example is shown below:

config.ini
# no section for preset values
actionName=example
[secret]
#empty value for secret values
passExample=

Initially, there are several configuration lines shown as the example, this should be changed. This file is not mandatory for a template. If the action code never uses configuration, this file can be removed.

Beware! Do not use any space to separate key, value and the "=" sign.

requirements.txt

This file is holding the project dependencies.

requirements.txt
# Bindings for the hermes protocol
hermes-python>=0.8

If there some libraries that needs to be installed in your code, append it here.

setup.sh

This file is used to set up the running environment for the action code. Most of the time, you don't need to modify it.

setup.sh
#!/usr/bin/env bash
set -e
if [ ! -e "./config.ini" ]; then
cp config.ini.default config.ini
fi
VENV=venv
if [ ! -d $VENV ]; then
PYTHON=`which python3.7`
if [ -f $PYTHON ]; then
virtualenv -p $PYTHON $VENV
else
echo "could not find python3.7"
fi
fi
. $VENV/bin/activate
pip3 install -r requirements.txt

Example - Joke App

This example was based on the previous python2.7 template, please make the changes regarding to the latest template.

In this example, we will make a joke app by using this rich template. It shall either fetch a random joke or use one from a given category. The designed workflow is shown below:

Joke action interaction work flow

Bundle Design

To be able to get intents from natural language, the first step is to create a bundle, in other words, your assistant. For this example, we will only have one intent, which is askJoke with one slot - category.

Intent

Slots

Description

askJoke

category

Fetch a joke

We highly recommend to those who would open source their App code repository to create a table describing the bundle design even if there's only one intent involved.

Here are all the joke categories: explicit, dev, developer, development, movie, cinema, food, celebrity, science, sport, sporting, animal, history, music, travel, travelling, career, money, fashion.

Action Code

For this action part of this example, you can find all the commits in this repository.

The main function of the action code is getting the category value then fetch the joke from a free API.

Thanks to https://api.chucknorris.io/ for providing the free joke API.

Following are three APIs we need to use:

Step 1/ Initialising an App repository

Create a Github repository, clone it to your local device. Init it with the contents here, then you should have the files organised like mentioned in the Template organisation section.

In this example, we named this repo as snips-app-joke-tuto. Then rename the action-app_template.py file to action-app_joke_tuto.py.

Step 2/ Filling action-app_joke_tuto.py with functional code

Since there are two pre-set sub callback functions, we will only keep one.

API request will be done by requests module, so this should be imported at the beginning of the code.

import requests

Then rename the class from Template to JokeTuto

class JokeTuto(object):

This project will only have one intent, so only one sub callback function should be kept. Let's rename it from intent_1_callback to askJoke_callback. Also, delete 2nd sub callback intent_2_callback .

Once again, do not forget to do the same thing in the master callback function master_intent_callback. Change the intent name to <snips_console_user_name>:askJoke. (We have changed it to coorfang:askJoke)

We are now good to write functional code in the askJoke_callback function:

# --> Sub callback function, one per intent
def askJoke_callback(self, hermes, intent_message):
# terminate the session first if not continue
hermes.publish_end_session(intent_message.session_id, "")
# action code goes here...
good_category = requests.get("https://api.chucknorris.io/jokes/categories").json();
category = None
if intent_message.slots.category:
category = intent_message.slots.category.first().value
# check if the category is valide
if category.encode("utf-8") not in good_category:
category = None
if category is None:
joke_msg = str(requests.get("https://api.chucknorris.io/jokes/random")\
.json().get("value"))
else:
joke_msg = str(requests.get("https://api.chucknorris.io/jokes/random?category={}".format(category))\
.json().get("value"))
# if need to speak the execution result by tts
hermes.publish_start_session_notification(intent_message.site_id, joke_msg, "Joke_Tuto_APP")

The sub callback function first close the session by giving an empty string. Then it fetch the category list of the jokes. This will be later used to check if a detected category value is validated. Slot value is checked just afterward.

By checking if the category is "None", we can choose to use different API requests.

Finally, we can play the joke by using the publish_start_session_notification function. This is the TTS function you are supposed to use whenever you need text-to-speech.

Step 3/ Added dependency to requirements.txt

In this example, we have used the requests module. But this package is not initially installed with python2. Do not forget to add it into requirements.txt

# Bindings for the hermes protocol
hermes-python>=0.1
requests
# More dependency goes here..

Step 4/ Get using config.ini

You may note that all the jokes are related to Chuck Norris. If you want to use someone's else name, we can add a config option to change the "protagonist" name.

First, let's modify the config.ini file to have this config entity, for this example we are going to change it to Jackie Chan : )

# no section for preset values
[secret]
#empty value for secret values
protagonist=Jackie Chan

Then let's add a line before the joke_msg is played.

new_people = self.config.get("secret").get("protagonist")
if new_people is not None and new_people is not "":
joke_msg = joke_msg.replace('Chuck Norris',new_people)

Step 5/ Debug and test

Use sam to install the test assistant, which contain the joke bundle (For the moment there is only a bundle, without any action code. Code will be tested locally before being bounded to the bundle):

sam install assistant

Then copy the App folder to /var/lib/snips/skill/ to test: (On Raspberry)

sudo cp -r snips-app-joke-tuto/ /var/lib/snips/skill/

Check if the action-app_joke_tuto.py and setup.sh has execution rights, if not:

chmod +x action-app_joke_tuto.py
chmod +x setup.sh

Install the virtual environment and dependencies and execute setup.sh:

sudo ./setup.sh

Manually stop the snips-skill-server:

sudo systemctl stop snips-skill-server

Then manually start it with -vvv to have more detailed logs:

snips-skill-server -vvv

Now you are able to see the output of the skill server.

By saying "Hey snips, please tell me a joke", you can see if there are some bugs in your code. Find and fix the existing problems.

When there aren't any bugs displayed and everything works fine, let's move to the deploying step.

Once everything is done, do not forget to restart the stopped snips-skill-server:

sudo systemctl restart snips-skill-server

Step 6/ Deploy the repository with bundle

This step is easy, all the things you need to do is copying and pasting your repository address to the bundle action page. (Be sure that you select Github as the Action Type). This is shown in the screenshot below.

Congratulations! You have now finished you first rich code snips App!