Skip to main content

Boilerplate for Snips skills in Python3

Project description

snips-skill

Helpers to keep Snips skills in Python3 free of boilerplate code.

Snips used to be a French company with a decent voice assistant. They were bought by Sonos and discontinued all services. Snips also created the Hermes MQTT protocol, which is compatible with the Rhasspy voice assistant.

A "skill" (in Snips parlance) is an MQTT client that may read sensors, do computations, or trigger devices.

Installation

pip install snips-skill

Requires Python 3.10+. Dependencies: paho-mqtt, pydantic, ply, croniter, toml, basecmd, decouple.

Contents

  • Clients: MqttClient, CommandLineClient, SnipsClient, Skill
  • Decorators: @topic, @intent, @min_confidence, @require_slot, @on_* session events
  • State tracking: StateAwareMixin, @when, @conditional
  • Scheduling: Scheduler, @cron, @delay
  • Multi-room: MultiRoomConfig
  • i18n: get_translations, room_with_article, room_with_preposition
  • Utilities: expr (boolean expression parser)

Plain MQTT clients

MqttClient is a thin wrapper around paho-mqtt. CommandLineClient adds argument parsing for connection settings (-H, -P, -T, -u, -p) and standard logging.

Usage

from snips_skill import CommandLineClient, topic

class Logger(CommandLineClient):
    @topic('#')
    def print_msg(self, userdata, msg):
        self.log.info("%s: %s", msg.topic, msg.payload[:64])

if __name__ == '__main__':
    Logger().run()

@topic decorator

Registers a callback for an MQTT topic (supports wildcards). Methods receive (self, userdata, msg); standalone functions receive (client, userdata, msg).

@topic("hermes/intent/#", qos=0, payload_converter=decode_json)
def handler(client, userdata, msg):
    ...

Snips clients

SnipsClient

Reads connection parameters from /etc/snips.toml (or $SNIPS_CONFIG). Provides session lifecycle methods:

Method Description
start_session(site_id, init) Start a session (action or notification)
speak(site_id, text) Say a one-time notification
end_session(session_id, text) End the session with an optional message
continue_session(session_id, text, ...) Continue with a question
play_sound(site_id, wav_data) Play a WAV sound
register_sound(name, wav_data) Register a TTS sound

Skill

Base class for Snips actions. Extends SnipsClient with config.ini support:

from snips_skill import Skill, intent

class HelloSkill(Skill):
    @intent('example:hello')
    def say_hello(self, userdata, msg):
        return 'Hello, there'

if __name__ == '__main__':
    HelloSkill().run()

Session event decorators

All decorators can be used on methods (expect self, userdata, msg) or standalone functions (expect client, userdata, msg). Multiple decorators on the same method are supported; set log_level=None on all but one to avoid duplicate logs.

Decorator Topic
@on_intent("intent_name") hermes/intent/...
@on_start_session() hermes/dialogueManager/startSession
@on_session_started() hermes/dialogueManager/sessionStarted
@on_continue_session() hermes/dialogueManager/continueSession
@on_end_session() hermes/dialogueManager/endSession
@on_session_ended() hermes/dialogueManager/sessionEnded
@on_hotword_detected() hermes/hotword/+/detected
@on_play_finished("site") hermes/audioServer/.../playFinished

@intent decorator

@intent-decorated callbacks receive msg.payload as an IntentPayload object — a parsed version of the JSON intent data. Slot values are converted to appropriate Python types.

The session outcome depends on the return value or exception:

Outcome How
Session ends with message Return a string or raise SnipsError
Session ends silently Return None with silent=True
Continue with question Raise SnipsClarificationError

@min_confidence(threshold, prompt)

Rejects intents below a confidence threshold. If the confidence score is lower than threshold, the user is asked prompt to confirm. Default prompt: "Pardon?".

Parameter Type Description
threshold float Minimum confidence score (0.0 – 1.0)
prompt str Question to re-ask when confidence is too low

@require_slot(slot, prompt, kind)

Ensures a required slot is present in the intent. If missing, the user is asked prompt. Optionally checks that the slot matches a specific kind.

Parameter Type Description
slot str Slot name to check
prompt str Question to re-ask when slot is missing
kind str or None Expected slot kind (optional)

Example

@intent('example:set-temperature')
@min_confidence(0.5)
@require_slot('temperature', 'Which temperature?')
def set_temp(self, userdata, msg):
    temp = msg.payload.slot_values['temperature'].value
    return f"Setting temperature to {temp}"

State tracking

StateAwareMixin tracks the last known state of MQTT topics in self.current_state. Configure a status_topic in config.ini under the [global] section:

[global]
status_topic = status/#

Subscribes to that topic with JSON decoding; every message updates self.current_state and triggers any matching @when/@conditional handlers. The publish() method also avoids redundant updates — if the payload matches the current state, the message is suppressed.

Usage

from snips_skill import Skill, StateAwareMixin, when

class MotionLight(Skill, StateAwareMixin):
    @when('sensor/motion > 0')
    def light_on(self):
        self.publish('cmd/light', 'on')

@when decorator

Triggers the handler whenever a boolean condition becomes true:

@when('sensor/motion > 0')
def motion_detected(self):
    ...  # switch light on

@conditional decorator

Triggers the handler whenever a relevant topic changes, passing True/False:

@conditional('sensor/temperature > 25 or sensor/humidity > 80')
def uncomfortable(self, on):
    if on:
        ...  # turn on AC
    else:
        ...  # turn off AC

Boolean expressions support comparisons (<, >, <=, >=, ==, !=, ~= regex), and, or, not, and parentheses.

Boolean expression grammar

The expression parser (expr.py) supports a simple grammar for use with @when and @conditional:

expr       → term
           | expr AND expr
           | expr OR expr
           | NOT expr
           | LPAREN expr RPAREN

term       → TOPIC LESS NUMBER
           | TOPIC LESS_EQUAL NUMBER
           | TOPIC GREATER_EQUAL NUMBER
           | TOPIC GREATER NUMBER
           | TOPIC EQUAL literal
           | TOPIC NOT_EQUAL literal
           | TOPIC REGEX_MATCH STRING

literal    → NUMBER | STRING

Precedence (highest to lowest): NOT > comparisons > AND > OR. See snips_skill/test_expr.py for examples.


Task scheduling

Scheduler mixin adds cron-style and delayed task execution.

@cron decorator

Run a method on a cron schedule:

from snips_skill import Skill, Scheduler, cron

class MySkill(Skill, Scheduler):
    @cron("0 6 * * *")   # every day at 6:00
    def morning_routine(self):
        ...

@delay decorator

Delay method execution by a duration (with optional randomization):

from snips_skill import delay

@delay(minutes=5, randomize=True)
def deferred_action(self):
    ...

Multi-room support

MultiRoomConfig maps site IDs to room names from config.ini sections. Each section with a site_id option (except [global] and other standard sections) becomes a named room:

[global]
status_topic = status/#      # not related to rooms; used by StateAwareMixin

[kitchen]
site_id = main
some_option = value

[bedroom]
site_id = bedroom
another_option = 42
from snips_skill import MultiRoomConfig

class MultiRoomSkill(Skill, MultiRoomConfig):
    LOCATION_SLOT = 'room'

    def handle_intent(self, payload):
        room = self.get_room(payload)
        config = self.get_room_config(payload)
        site = self.get_site_id(payload)

Key methods: get_room(), get_current_room(), get_room_name(), get_room_config(), get_site_id(), all_rooms(), in_current_room().


Internationalization

Internationalization uses Python's standard gettext module. Run xgettext on your Python sources to produce a .pot template file, then create .po files for each locale and compile them to .mo files:

your-skill/
├── locale/
│   ├── my_skill.pot                 # template (generated by xgettext)
│   ├── de_DE/
│   │   └── LC_MESSAGES/
│   │       └── my_skill.mo         # compiled from my_skill.po
│   └── fr_FR/
│       └── LC_MESSAGES/
│           └── my_skill.mo
└── skill.py                         # calls get_translations(__file__, "my_skill")

The .po file format follows standard gettext conventions:

# my_skill.po
msgid "the kitchen"
msgstr "die Küche"

msgid "in the kitchen"
msgstr "in der Küche"

See the Makefile for the workflow used by this library: make messages generates the .pot file from source, and make dist compiles .po to .mo before building.

After installing translations with get_translations(__file__, "my_skill"), helper functions provide translated room names with articles and prepositions (supports gendered languages like German):

from snips_skill import room_with_article, room_with_preposition

room_with_article("kitchen")       # "the kitchen" / "die Küche"
room_with_preposition("kitchen")   # "in the kitchen" / "in der Küche"

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

snips_skill-0.1.32.tar.gz (32.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

snips_skill-0.1.32-py3-none-any.whl (34.6 kB view details)

Uploaded Python 3

File details

Details for the file snips_skill-0.1.32.tar.gz.

File metadata

  • Download URL: snips_skill-0.1.32.tar.gz
  • Upload date:
  • Size: 32.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for snips_skill-0.1.32.tar.gz
Algorithm Hash digest
SHA256 c79e402756816d11f641b49eb7b9343ec284156e8e382ee59a8606e2a14ec289
MD5 28414a0c7274fc11a21ab5b04a03ba18
BLAKE2b-256 da8dd0c8e6a5ca02416511ce4d9426854fa9f9ac23b336a1457ebc62834e82b9

See more details on using hashes here.

File details

Details for the file snips_skill-0.1.32-py3-none-any.whl.

File metadata

  • Download URL: snips_skill-0.1.32-py3-none-any.whl
  • Upload date:
  • Size: 34.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for snips_skill-0.1.32-py3-none-any.whl
Algorithm Hash digest
SHA256 85ea2f6cdbdb6702bfc3f3ed2c59290b88e9a822edcce1c7382e4cf5920ed25e
MD5 7c6f1d22358079cf30f52723dbf9a45d
BLAKE2b-256 d9a2dedb8ca69eb03656a582dff18dcad54eea5f8673a5f2b89f251c1f3d10d3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page