Skip to main content

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
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(StateAwareMixin, Skill):
    @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(Scheduler, Skill):
    @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(MultiRoomConfig, Skill):
    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"

Release files for snips-skill 0.1.33

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for snips-skill 0.1.33
File Size Uploaded
snips_skill-0.1.33.tar.gz 41.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for snips-skill 0.1.33
File Interpreter ABI Platform
snips_skill-0.1.33-py3-none-any.whl Python 3 none any Details

Total release size: 88.6 kB

Release files / snips_skill-0.1.33.tar.gz

Download URL snips_skill-0.1.33.tar.gz
Size 41.6 kB
Tags Source
SHA-256 checksum
How to use checksums
9d28ddcae839e151a8ccfd0990d1a629577f1ab28266944dc02d7e1ebe2cc780
BLAKE2b-256 checksum
How to use checksums
2167d5e3577c469f03fe385633fa2213b306736e7af42738eb739b0399c8168c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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}

Release files / snips_skill-0.1.33-py3-none-any.whl

Download URL snips_skill-0.1.33-py3-none-any.whl
Size 47.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
77d4331f510eb26157d7204462b9c51db42e9eafc70f3587337a8fbb0cf58bcd
BLAKE2b-256 checksum
How to use checksums
7f8908c9155db6a1f117d397cb2bb9744eb8b7ac9f9f15099da4db593ccbc446
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","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}

Release history Release notifications | RSS feed

This release

0.1.33 This release

2 release files

0.1.32

2 release files

0.1.31

2 release files

0.1.30

2 release files

0.1.29

2 release files

0.1.27

2 release files

0.1.25

2 release files

0.1.24

2 release files

0.1.21

2 release files

0.1.18

2 release files

0.1.17

2 release files

0.1.16

2 release files

0.1.15

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page